We shall build a fun Go app that accepts city names from a user and displays the current weather forecast in those cities, to the user.
In a previous article we learned how to use The Google Geocoding API in Go. Here too, we shall use Geocoding to help us convert addresses (like “1600 Amphitheatre Parkway, Mountain View, CA”) into geographic coordinates (like latitude 37.423021 and longitude -122.083739). Next, we shall use the weather forecast api to find out the temperature etc. at the cities entered by our user.
Register for an account at Forecast for Developers
Go to https://darksky.net/dev/register and register for an account. We shall use their free plan that allows us to use 1000 calls to their API per day. As such, there is no need to enter your credit card details. Also, this page gives you your very own API key that you will use in your program.
Study the API documentation
We need to read thro’ the API documentation of the Forecast for Developers.
Very briefly:
The Forecast Call
https://api.darksky.net/forecast/[key]/[latitude],[longitude]
key
should be your API key as mentioned above. LATITUDE
and LONGITUDE
should be the geographic coordinates of a location in decimal degrees.
The response will be a JSON-formatted object with the following properties defined:
latitude
: The requested latitude.longitude
: The requested longitude.timezone
: The timezone name for the requested location (e.g. America/New_York).offset
: The current timezone offset in hours from GMT.currently
: A data point (see below) containing the current weather conditions at the requested location.
Data Points
A data point object represents the various weather phenomena occurring at a specific instant of time, and has many varied properties. All of these properties (except time
) are optional, and will only be set if we have that type of information for that location and time.
The following JSON Schema of the API is very informative:
We shall use the above schema in our program.
Our app weather.go – first cut
Create a folder C:/go_projects/go/src/github.com/SatishTalim/weather
and inside this folder write the program weather.go
as follows:
package main import ( "encoding/json" "fmt" "io/ioutil" "log" "net/http" "net/url" "sync" ) type DataPoint struct { Time float64 Summary string Icon string SunriseTime float64 SunsetTime float64 PrecipIntensity float64 PrecipIntensityMax float64 PrecipIntensityMaxTime float64 PrecipProbability float64 PrecipType string PrecipAccumulation float64 Temperature float64 TemperatureMin float64 TemperatureMinTime float64 TemperatureMax float64 TemperatureMaxTime float64 DewPoint float64 WindSpeed float64 WindBearing float64 CloudCover float64 Humidity float64 Pressure float64 Visibility float64 Ozone float64 } type Forecast struct { Latitude float64 Longitude float64 Timezone string Offset float64 Currently DataPoint Junk string } func main() { // Create a wait group to manage the goroutines. var waitGroup sync.WaitGroup // Perform 4 concurrent queries waitGroup.Add(4) for query := 0; query < 4; query++ { go Get(query, &waitGroup) } // Wait for all the queries to complete. waitGroup.Wait() fmt.Printf("All Queries Completed") } // Get is a function that is launched as a goroutine func Get(query int, waitGroup *sync.WaitGroup) { // Decrement the wait group count so the program knows this // has been completed once the goroutine exits. defer waitGroup.Done() addr := [4]string{"Pune,India", "Franklin,TN", "Sydney,Australia", "Vientiane,Lao PDR"} // Geocoding API // QueryEscape escapes the addr string so // it can be safely placed inside a URL query // safeAddr := url.QueryEscape(addr) safeAddr := url.QueryEscape(addr[query]) fullUrl := fmt.Sprintf("http://maps.googleapis.com/maps/api/geocode/json?sensor=false&address=%s", safeAddr) // Build the request req, err1 := http.NewRequest("GET", fullUrl, nil) if err1 != nil { panic(err1) } // For control over HTTP client headers, // redirect policy, and other settings, // create a Client // A Client is an HTTP client client := &http.Client{} // Send the request via a client // Do sends an HTTP request and // returns an HTTP response resp, err2 := client.Do(req) if err2 != nil { panic(err2) } // Callers should close resp.Body // when done reading from it // Defer the closing of the body defer resp.Body.Close() // Read the content into a byte array body, dataReadErr := ioutil.ReadAll(resp.Body) if dataReadErr != nil { panic(dataReadErr) } res := make(map[string][]map[string]map[string]map[string]interface{}, 0) // We will be using the Unmarshal function // to transform our JSON bytes into the // appropriate structure. // The Unmarshal function accepts a byte array // and a reference to the object which shall be // filled with the JSON data (this is simplifying, // it actually accepts an interface) json.Unmarshal(body, &res) // lat, lng as float64 lat, _ := res["results"][0]["geometry"]["location"]["lat"] lng, _ := res["results"][0]["geometry"]["location"]["lng"] // Forecast API // %.13f is used to convert float64 to a string url := fmt.Sprintf("https://api.darksky.net/forecast/yourapikey/%.13f,%.13f?units=ca", lat, lng) resp, err := http.Get(url) if err != nil { log.Fatal(err) } defer resp.Body.Close() fbody, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal(err) } var f Forecast json.Unmarshal(fbody, &f) fmt.Println("The Weather at ", addr[query]) fmt.Println("Timezone = ", f.Timezone) fmt.Println("Temp in Celsius = ", f.Currently.Temperature) fmt.Println("Summary = ", f.Currently.Summary) }
Concurrent Queries
In the above program, we run queries concurrently. Let’s see how:
// Create a wait group to manage the goroutines. var waitGroup sync.WaitGroup
The WaitGroup Example in the documentation is very informative.
A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls Done when finished. At the same time, Wait can be used to block until all goroutines have finished.
Run the program
In the folder C:/go_projects/go/src/github.com/SatishTalim/weather
type:
$ go run weather.go
In the command window you should see something like this:
The Weather at Pune,India Timezone = Asia/Kolkata Temp = 26.59 Celsius Summary = Clear The Weather at Vientiane,Lao PDR Timezone = Asia/Vientiane Temp = 35.59 Celsius Summary = Clear The Weather at Franklin,TN Timezone = America/Chicago Temp = 16.10 Celsius Summary = Clear The Weather at Sydney,Australia Timezone = Australia/Sydney Temp = 24.86 Celsius Summary = Partly Cloudy All Queries Completed
What next?
Modify the above program, to accept multiple city addresses on a webpage. Display the result as a table on a new webpage. Deploy this app to the Google App Engine.
Here’s a sample screenshot:
I have deployed this app to the Google App Engine and it is running here. You can use this code.
You can learn more about writing and deploying web apps with Go from my new book.
I hope you’ve enjoyed this post, please leave any feedback in the comments section.
Would I be correct in saying that the WaitGroup ideally shouldn’t be passed to the Get func? By doing so, you’ve meant that if you want to call Get synchronously, you have to pass a wait group to it, which doesn’t seem right to me. Would it be better to wrap the call to Get in an anonymous func and put the defer wait group done code within that func?
Jon, check out this https://gist.github.com/SatishTalim/10018527
Is that what you were looking for?
Keep the “Get” code in it’s own func as you already had, but correct that’s what I meant.