Skip to content
Satish Talim edited this page Aug 18, 2013 · 6 revisions

Roadmap: Building the Sinatra app

Idea for the program

A friend recently saw a picture posted on Facebook by a friend and wanted to find out where that picture was taken. The picture was of someone in a park and he could not pinpoint exactly which park it was, but had a general idea. He asked me if there was a way for him to see the location of the photo on a map.

Preamble

Before you can determine the location of a photo, the photo has to be geotagged. Geotagged basically means that the longitude and latitude of the photo has been stored in the photo metadata. The metadata is the invisible part of the photo called EXIF data. Depending on the camera, EXIF data will store the current state of the camera when the photo was taken including date and time, shutter speeds, focal lengths, flash, lens type, location data, etc.

Of course, the only way you’ll see where a picture was taken is if the camera is GPS enabled. If you have a camera that doesn’t have any type of GPS option, then there won’t be any location data in the EXIF data. This is true of most SLR cameras. However, if the photo was taken with a smartphone and location services are enabled, then the GPS coordinates of the phone will be captured when you snap a picture.

Building the app

Install the gem

We need to install a gem to read EXIF data from a given image. We shall use the exifr gem. You can read the details of the "exifr gem"

$ gem install exifr

You can run $ bundle install which will do this for you, as per the Gemfile.

Program exifmap_sinatra.rb

# exifmap_sinatra.rb
require 'sinatra'
require 'exifr'

not_found do
  erb :'404'
end

error do
  'Sorry there was a nasty error - ' + env['sinatra.error'].name
end

get '/' do
  erb :index
end

post '/show' do
  begin
    if params[:photo]
      photo = EXIFR::JPEG.new(params[:photo][:tempfile])
      lat = photo.gps.latitude
      lng = photo.gps.longitude
      defaults = {:zoom => 5, :size => [600, 300], :maptype => 'roadmap'}
      @photo_map = "http://maps.googleapis.com/maps/api/staticmap?sensor=false&zoom=#{defaults[:zoom]}&size=#{defaults[:size][0]}x#{defaults[:size][1]}&maptype=#{defaults[:maptype]}&center=#{lat},#{lng}&markers=color:blue%7Clabel:I%7C#{lat},#{lng}"
      erb :show
    end
  rescue NoMethodError
    halt erb(:nomethod)
  end
end 

Code Explanation

The Google Static Maps API lets you embed a Google Maps image on your web page without requiring JavaScript or any dynamic page loading. The Google Static Map service creates your map based on URL parameters sent through a standard HTTP request and returns the map as an image you can display on your web page.

We create the string photo_map based on the above API, as follows:

  • The base URL is http://maps.googleapis.com/maps/api/staticmap?sensor=false where applications that determine the user's location via a sensor must pass sensor=true within the Static Maps API request URL. If your application does not use a sensor, pass sensor=false. The sensor parameter is required.

The additional parameters used in photo_map are:

  • zoom (required) defines the zoom level of the map, which determines the magnification level of the map. This parameter takes a numerical value corresponding to the zoom level of the region desired. Zoom levels between 0 (the lowest zoom level, in which the entire world can be seen on one map) to 21+ (down to individual buildings) are possible within the default roadmap maps view.
  • size (required) defines the rectangular dimensions of the map image. This parameter takes a string of the form {horizontal_value}x{vertical_value}. For example, 600x300 defines a map 600 pixels wide by 300 pixels high.
  • maptype (optional) defines the type of map to construct. There are several possible maptype values, including roadmap, satellite, hybrid, and terrain.
  • center (required) defines the center of the map, equidistant from all edges of the map. This parameter takes a location as either a comma-separated {latitude,longitude} pair (e.g. "40.714728,-73.998672") or a string address (e.g. "city hall, new york, ny") identifying a unique location on the face of the earth.
  • markers (optional) define one or more markers to attach to the image at specified locations. This parameter takes a single marker definition with parameters separated by the pipe character (| or %7C).

The markers parameter takes set of value assignments (marker descriptors) of the following format: markers=markerStyles|markerLocation1| markerLocation2|... etc.

The set of markerStyles is declared at the beginning of the markers declaration and consists of zero or more style descriptors separated by the pipe character (|), followed by a set of one or more locations also separated by the pipe character (|).

The marker style descriptors contain the following key/value assignments:

  • size: (optional) specifies the size of marker from the set {tiny, mid, small}. If no size parameter is set, the marker will appear in its default (normal) size.
  • color: (optional) specifies a 24-bit color (example: color=0xFFFFCC) or a predefined color from the set {black, brown, green, purple, yellow, blue, gray, orange, red, white}.
  • label: (optional) specifies a single uppercase alphanumeric character from the set {A-Z, 0-9}.

Each marker descriptor must contain a set of one or more locations defining where to place the marker on the map. These locations may be either specified as latitude/longitude values or as addresses.

Other code

Gemfile

source "http://rubygems.org"
gem 'sinatra'
gem 'exifr'

config.ru

require './exifmap_sinatra'
run Sinatra::Application

Procfile

web: bundle exec ruby exifmap_sinatra.rb -p $PORT

README.md

GPS Location and Map from Photo
===============================

Brief Description
-----------------

This is a introductory level project wherein we build a Sinatra app that extracts the geo data, namely latitude and longitude, from a given photo and then shows the location where the photo was snapped, on a map.

References
----------

The Google Static Maps API - https://developers.google.com/maps/documentation/staticmaps/
exifr gem - https://rubygems.org/gems/exifr

Wiki
----

Here we explain the logic of the Sinatra app that we have built - 

Views folder

index.erb

<form action="/show" method="post" enctype="multipart/form-data">
  Select a photo: <input type="file" name = "photo" id="photo" /><br />
  <input type="submit" value="Upload" />
</form>

show.erb

<div>
  <h2>Your photo was snapped here!</h2>
    <img src=<%= @photo_map %> alt="Photo location" />
  <p><a href="/">Back</a></p>
</div>
<div id="footer">
  <p><b>A Fun Sinatra App for accessing photo location and displaying the location map by RubyLearning 18 Aug. 2013</b>.</p>
</div>

404.erb

<div>
  <h1>A Sinatra app to display location where photo was snapped</h1>
  <p>I'm sorry but the "The Sinatra app to access Photo Location Service is not accessible from the folder you typed in.</p>
  <p>The correct URL is: <a href="http://localhost:4567/">http://localhost:4567/</a></p>
  <p><a href="/">Back</a></p>
</div>
<div id="footer">
  <p><b>A Fun Sinatra App for displaying location where a photo was snapped by RubyLearning 18 Aug. 2013</b>.</p>
</div>

nomethod.erb

<div>
  <h1>A Sinatra app to display location where photo was snapped</h1>
  <p>I'm sorry but the photo you uploaded has no location data.</p>
  <p><a href="/">Back</a></p>
</div>
<div id="footer">
  <p><b>A Fun Sinatra App for displaying location where a photo was snapped by RubyLearning 18 Aug. 2013</b>.</p>
</div>

Finally, our app displays the map in a new browser window.