Recently I was building a small library to extract information from image files. While doing so I thought a simple site to extract GPS information from images would be useful to build as well as to describe how it was created using Clojure.
The source code for this project is available on GitHub at https://github.com/bradlucas/imagelocation while a running example is available at http://imagelocation.beaconhill.com/. If you don't have a photo handy and would quickly like to see what the site returns see the About page http://imagelocation.beaconhill.com/about.
I'll assume that you the reader have some experience with Clojure and have build something in the language. With that in mind I'll just go through the steps I went through to build up the application.
They are as follows:
Let's start with a basic project.
$ lein new imagelocation
https://github.com/bradlucas/imagelocation/commit/0ccb62ab4996b85bcd31931e972b89b086fd3ade
Here we add the Compojure library and get a simple route working.
Add the following to the project.clj file's dependencies.
[compojure "1.6.1"]
[ring/ring-defaults "0.3.2"]
Also, add the following to the project.clj file.
:repl-options {:init-ns imagelocation.core}
:plugins [[lein-ring "0.12.5"]]
:ring {:handler imagelocation.handler/app}
:profiles
{:dev {:dependencies [[javax.servlet/servlet-api "2.5"]
[ring/ring-mock "0.3.2"]]}}
Create a file called handler.clj add a require entries and an initial app-routes and app.
(ns imagelocation.handler
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]))
(defroutes app-routes
(GET "/" [] "Hello World")
(route/not-found "Not Found"))
(def app
(wrap-defaults app-routes site-defaults))
Details: https://github.com/bradlucas/imagelocation/commit/9581ccdfc6d464b57fe165d6332160d51d1d3d02
Now test this step with:
$ lein ring server
Using lein ring server
is fine to get started but we'll want to run a stand alone web app. Also, we'll need to run our system from the command line. To do this see the following commit:
https://github.com/bradlucas/imagelocation/commit/026ac4c4ab4fd0bc092f351e5766aef9d5c411ea
Here we are adding the following libraries.
[ring/ring-jetty-adapter "1.7.1"]
[org.clojure/tools.cli "0.4.2"]
The ring-jetty-adapter
lets use run standalone with a statement like:
(jetty/run-jetty handler/app {:port 4002}
The org.clojure/tools.cli
library lets us process command line arguments easily. See the above mentioned commit for details on how to check for a -f filename
to process a single file.
The following commit introduces the main functionality to extract location information from images.
See this commit:
https://github.com/bradlucas/imagelocation/commit/e39a49516f97c8f899b0d58e0ec0dc77a483cf78
The library the application is using is the metadata-extractor
from Drew Noakes https://github.com/drewnoakes/metadata-extractor.
To start a reference to the library is added to the project.clj file.
[com.drewnoakes/metadata-extractor "2.12.0"]
Then see the image.clj
file https://github.com/bradlucas/imagelocation/blob/e39a49516f97c8f899b0d58e0ec0dc77a483cf78/src/imagelocation/image.clj.
The main routine to extract the data is image-data
.
(defn image-data
"Return map of all image data fields
"
[filename]
(->> (io/file filename)
ImageMetadataReader/readMetadata
.getDirectories
(map #(.getTags %))
(into {} (map extract-from-tag))))
The output of which is passed to get-location-data
to remove just the GPS fields.
(defn get-location-data
"Return `GPS Latitude` and `GPS Longitude` values in a map
"
[filename]
(let [info (image-data filename)]
{:lat (get info "GPS Latitude")
:lng (get info "GPS Longitude")}))
There are some routines to convert the three field lat/lng value strings to single numbers in the file as well as a routine to create a Google Map link.
The next to last step is to add a simple UI. I choose to use the Selmer libary for templating and a basic Bootstrap template from [Bootswatch])https://bootswatch.com/lux/).
The commit which introduces the files is https://github.com/bradlucas/imagelocation/commit/ce92691f84ee620cb5d353bd73441b3f3f398779.
Adding Selmer consists of adding the following to your project.clj file.
[selmer "1.12.12"]
Getting things setup is a bit more involved than previous steps. It might be worth while looking carefully over the above mentioned commit. What you'll need is the following:
view
If you are following along and building as you go focus on getting a static page working first. This means the base.html
and index.html
files working with the associated public files.
Then focus on the handlers to process the upload. This may be tricky at first but review the repo for the solution.
The last step is after many tweaks to make the system more robust and look better. Feel free to step through the commits to see the details.
The final version as of this writing is version 1.0 and available on this branch https://github.com/bradlucas/imagelocation/tree/release/1.0.
Also, a running version to try is availabe at http://imagelocation.beaconhill.com/