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.
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
longitude coordinates for the location where you'd like the weather reports for.
Register for a key here:
With the requirement that
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
lng values for an associated `zipcode'.
As a first step, find the
lng values for your given
zipcode and then build the Dark Sky API url manually with your new
key. The format of which is:
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:
To 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
(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.
Next, call into the zipcode namespace to get the
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])))
-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: