I stumbled upon this service called Dark Sky the other day which supports an API to return weather reports. I thought it would be interesting to create a small command line application which would query this API and print out a short summary weather report. Yesterday, I took up the challenge to build such an application in Clojure.
The results are up on GitHub in the following repo.
https://github.com/bradlucas/weather
For those who are interested I'll go through a process to build the application.
First, investigate the Dark Sky API. The documention is here:
The main function is here:
It is fairly straightforward. You register and get a key
which you pass to a web service url which returns data in JSON format. In addition to the key
you need to pass along latitude
and longitude
coordinates for the location where you'd like the weather reports for.
Register for a key here:
With the requirement that lat
and lng
values are needed, I considered if it was possible to convert a zipcode to these values. I'm figuring that a zipcode
would be a better input parameter for a user.
Searching around it looks like someone has put the needed mapping together in a Gist.
This looked like it would work. Read the raw file, parse the csv and return the lat
and lng
values for an associated `zipcode'.
As a first step, find the lat
and lng
values for your given zipcode
and then build the Dark Sky API url manually with your new key
. The format of which is:
https://api.darksky.net/forecast/KEY/LAT,LNG
You can put that in a browser and make sure it works. Investigate the results. You'll see there is a lot of data.
For my purposes I identified the following as useful:
lat
and lng
valuesTo make requests from other sites I'm using the clj-http library. It's get function returns a map which you'll want to get the value for :body. This data is in csv format so you need to parse it. Here I'm using clojure.data.csv to parse the data. When you try it you'll see that it returns a sequence of vectors which include each row's data elements. Since the first is the zipcode filter on that and return the second and third elements as they are the lat
and lng
valuues.
(ns weather.zipcode
(:require [clj-http.client :as client]
[clojure.data.csv :as csv]))
(def zip-data-url
"Found data at https://gist.github.com/erichurst/7882666"
"https://gist.githubusercontent.com/erichurst/7882666/raw/5bdc46db47d9515269ab12ed6fb2850377fd869e/US%20Zip%20Codes%20from%202013%20Government%20Data")
(defn get-zipcode-location
"Return lat, lng for zipcode"
[zipcode]
(let [m (csv/read-csv (:body (client/get zip-data-url)))
[_ lat lng] (first (filter #(= zipcode (first %)) m))]
[lat lng]))
The data namespace holds the main getter function. Here we need to get our key. I'm using environ to pull the value from the environment. This means you need to setup an environment variable as follows before running the application.
export DARKSKY_KEY=KEY-VALUE
Next, call into the zipcode namespace to get the lat
and lng
values with get-zipccode-location
function. With that the API url can be built.
Since, the API returns JSON you'll want to convert that to something easy use in Clojure. The Cheshire library is good for this and the function parse-string
converts the JSON data to a Clojure map.
Once in a map it is easy to get-in
the values.
(ns weather.data
(:require [clj-http.client :as client]
[weather.zipcode :as zip]
[cheshire.core :as cheshire :refer [parse-string]]
[environ.core :refer [env]]))
(def darksky-key
"Configure your DarkSky key as an environment variable"
(env :darksky-key))
(defn get-weather-data
"Contact darksky for the data"
[lat lng]
(let [url (format "https://api.darksky.net/forecast/%s/%s,%s" darksky-key lat lng)]
(client/get url)))
(defn weather-report [zipcode]
"Return a report for the weather data"
(let [[lat lng] (zip/get-zipcode-location zipcode)
m (cheshire/parse-string (:body (get-weather-data lat lng)))]
(let [current (get-in m ["currently" "summary"])
soon (get-in m ["hourly" "summary"])
later (get-in m ["daily" "summary"])]
[current soon later])))
Lastly, the -main
function is where to accept the command line parameter zipcode, call into data/weather-report
and then display the results.
(ns weather.core
(:require [weather.data :as data])
(:gen-class))
(defn -main
"Accept a zipcode on the command line and produce a weather report ensure single argument"
[& args]
(if args
(let [zipcode (first args)]
(let [[current soon later] (data/weather-report zipcode)]
(println (format "Zipcode : %s" zipcode))
(println (format "Current : %s" current))
(println (format "Soon : %s" soon))
(println (format "Later : %s" later))))
(println "Usage: weather ZIPCODE")))
Here is an example run for New York city.
$ lein run 10001
Zipcode : 10001
Current : Partly Cloudy
Soon : Mostly cloudy starting this afternoon.
Later : Rain tomorrow and Saturday, with temperatures falling to 78°F on Tuesday.
Some other large city zipcodes for trial:
|-------------+----------|
| City | Zip Code |
|-------------+----------|
| New York | 10002 |
| Boston | 02114 |
| Los Angeles | 90011 |
| Chicago | 60629 |
| Austin | 73301 |
|-------------+----------|
For subsequent versions of this application, I foresee a few things: