Let’s Have Some Fun With Gmail & Go

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:

weather

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.

3 thoughts on “Let’s Have Some Fun With Gmail & Go

  1. 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?

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.