Weather Forecast Webapp Revisited

Weather Forecast Webapp Revisited

One year ago, I published a tutorial about building a weather forecasting web app, powered by Yahoo’s APIs and the browser’s built in geolocation capabilities. However, recently Yahoo discontinued these free APIs, so today we are going to convert the web app to a different service – OpenWeatherMap.

The OpenWeatherMap API

OpenWeatherMap is not only free to use, but it also returns the forecast for the next five days and does with a single API request what Yahoo could only do with two. The API directly takes a set of geographic coordinates and returns weather data (no need to first find the city). These features make it pain-free to use and I am only sorry that I didn’t know about this service earlier. Here is an example response:

{
    "cod": "200",
    "message": 0.0074,
    "city": {
        "id": 726048,
        "name": "Varna",
        "coord": {
            "lon": 27.91667,
            "lat": 43.216671
        },
        "country": "BG",
        "population": 0
    },
    "cnt": 41,
    "list": [{
            "dt": 1369224000,
            "main": {
                "temp": 295.15,
                "temp_min": 293.713,
                "temp_max": 295.15,
                "pressure": 1017.5,
                "sea_level": 1023.54,
                "grnd_level": 1017.5,
                "humidity": 94,
                "temp_kf": 1.44
            },
            "weather": [{
                    "id": 800,
                    "main": "Clear",
                    "description": "sky is clear",
                    "icon": "02d"
                }
            ],
            "clouds": {
                "all": 8
            },
            "wind": {
                "speed": 5.11,
                "deg": 155.502
            },
            "sys": {
                "pod": "d"
            },
            "dt_txt": "2013-05-22 12:00:00"
        }

        // 40 more items here..

    ]
}

A single API call returns geographic information, city name, country code and detailed weather forecast. The weather predictions are returned in the list property as an array and are spaced three hours apart. In our code, we will have to loop through this list, and present the forecast as a series of slides. The good news is that a lot of the work we did in the previous tutorial can be reused.

Weather Forecast Web App

Weather Forecast Web App

What needs to be changed

We won’t be starting from scratch – we will be reusing the HTML and the design from the last tutorial. On the HTML part everything is nearly the same as in the original, with the exception that I’ve upgraded to the latest jQuery version, and have imported the moment.js (quick tip) date/time library that we will use to present the time of the forecasts.

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Weather Forecast App Revisited | Tutorialzine Demo</title>

        <!-- The stylesheet -->
        <link rel="stylesheet" href="assets/css/styles.css" />

        <!-- Google Fonts -->
        <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Playball|Open+Sans+Condensed:300,700" />

        <!--[if lt IE 9]>
          <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->
    </head>

    <body>

        <header>
            <h1>Weather Forecast</h1>
        </header>

        <div id="weather">

            <ul id="scroller">
                <!-- The forecast items will go here -->
            </ul>

            <a href="#" class="arrow previous">Previous</a>
            <a href="#" class="arrow next">Next</a>

        </div>

        <p class="location"></p>

        <div id="clouds"></div>

        <!-- JavaScript includes - jQuery, moment.js and our own script.js -->
        <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.0.0/moment.min.js"></script>
        <script src="assets/js/script.js"></script>

    </body>
</html>

What needs to be rewritten though, is our JavaScript code. OpenWeatherMap is simpler to use and takes the coordinates from the geolocation api directly, so I have removed a lot of the old code. Another benefit is that there is no need to register for an API key on OpenWeatherMap, which means we can jump directly to the source:

assets/js/script.js

$(function(){

    /* Configuration */

    var DEG = 'c';  // c for celsius, f for fahrenheit

    var weatherDiv = $('#weather'),
        scroller = $('#scroller'),
        location = $('p.location');

    // Does this browser support geolocation?
    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(locationSuccess, locationError);
    }
    else{
        showError("Your browser does not support Geolocation!");
    }

    // Get user's location, and use OpenWeatherMap
    // to get the location name and weather forecast

    function locationSuccess(position) {

        try{

            // Retrive the cache
            var cache = localStorage.weatherCache && JSON.parse(localStorage.weatherCache);

            var d = new Date();

            // If the cache is newer than 30 minutes, use the cache
            if(cache && cache.timestamp && cache.timestamp > d.getTime() - 30*60*1000){

                // Get the offset from UTC (turn the offset minutes into ms)
                var offset = d.getTimezoneOffset()*60*1000;
                var city = cache.data.city.name;
                var country = cache.data.city.country;

                $.each(cache.data.list, function(){
                    // "this" holds a forecast object

                    // Get the local time of this forecast (the api returns it in utc)
                    var localTime = new Date(this.dt*1000 - offset);

                    addWeather(
                        this.weather[0].icon,
                        moment(localTime).calendar(),   // We are using the moment.js library to format the date
                        this.weather[0].main + ' <b>' + convertTemperature(this.main.temp_min) + '°' + DEG +
                                                ' / ' + convertTemperature(this.main.temp_max) + '°' + DEG+'</b>'
                    );

                });

                // Add the location to the page
                location.html(city+', <b>'+country+'</b>');

                weatherDiv.addClass('loaded');

                // Set the slider to the first slide
                showSlide(0);

            }

            else{

                // If the cache is old or nonexistent, issue a new AJAX request

                var weatherAPI = 'http://api.openweathermap.org/data/2.5/forecast?lat='+position.coords.latitude+
                                    '&lon='+position.coords.longitude+'&callback=?'

                $.getJSON(weatherAPI, function(response){

                    // Store the cache
                    localStorage.weatherCache = JSON.stringify({
                        timestamp:(new Date()).getTime(),   // getTime() returns milliseconds
                        data: response
                    });

                    // Call the function again
                    locationSuccess(position);
                });
            }

        }
        catch(e){
            showError("We can't find information about your city!");
            window.console && console.error(e);
        }
    }

    function addWeather(icon, day, condition){

        var markup = '<li>'+
            '<img src="assets/img/icons/'+ icon +'.png" />'+
            ' <p class="day">'+ day +'</p> <p class="cond">'+ condition +
            '</p></li>';

        scroller.append(markup);
    }

    /* Handling the previous / next arrows */

    var currentSlide = 0;
    weatherDiv.find('a.previous').click(function(e){
        e.preventDefault();
        showSlide(currentSlide-1);
    });

    weatherDiv.find('a.next').click(function(e){
        e.preventDefault();
        showSlide(currentSlide+1);
    });

    // listen for arrow keys

    $(document).keydown(function(e){
        switch(e.keyCode){
            case 37: 
                weatherDiv.find('a.previous').click();
            break;
            case 39:
                weatherDiv.find('a.next').click();
            break;
        }
    });

    function showSlide(i){
        var items = scroller.find('li');

        if (i >= items.length || i < 0 || scroller.is(':animated')){
            return false;
        }

        weatherDiv.removeClass('first last');

        if(i == 0){
            weatherDiv.addClass('first');
        }
        else if (i == items.length-1){
            weatherDiv.addClass('last');
        }

        scroller.animate({left:(-i*100)+'%'}, function(){
            currentSlide = i;
        });
    }

    /* Error handling functions */

    function locationError(error){
        switch(error.code) {
            case error.TIMEOUT:
                showError("A timeout occured! Please try again!");
                break;
            case error.POSITION_UNAVAILABLE:
                showError('We can\'t detect your location. Sorry!');
                break;
            case error.PERMISSION_DENIED:
                showError('Please allow geolocation access for this to work.');
                break;
            case error.UNKNOWN_ERROR:
                showError('An unknown error occured!');
                break;
        }

    }

    function convertTemperature(kelvin){
        // Convert the temperature to either Celsius or Fahrenheit:
        return Math.round(DEG == 'c' ? (kelvin - 273.15) : (kelvin*9/5 - 459.67));
    }

    function showError(msg){
        weatherDiv.addClass('error').html(msg);
    }

});

The majority of the changes are to the locationSuccess() function, where we make a request to the API and call addWeather(). The latter also needed some changes, so that it uses the icon code contained in the weather data to present the correct image from the assets/img/icons folder. See the list of the icons (day and night versions) and weather codes in the OpenWeatherMap docs.

Another thing worth noting is the way I am using the persistent localStorage object to cache the result from the API for 30 minutes which limits the number of requests that go to OpenWeatherMap, so that everyone can get their fair share.

With this our Weather web app is ready! Have any questions? Hit the comment section below.

Join our newsletter and get our PSDs!18,481 people learn about HTML5, JS and more. Join them!

by Martin Angelov

Martin is a web developer with an eye for design from Bulgaria. He founded Tutorialzine in 2009 and it still is his favorite side project.

19 Comments

  1. Linas says:

    Nice! Something new.

  2. Kalee says:

    I'm in Idaho and it detects as Nebraska. An input field for manual correction of geolocation would be nice to have added. Very nice work though.

  3. Utilisatrice says:

    Great, really !

  4. Amit says:

    As usual, this time also i loved the UI. I wish i could creat UI's like you :P :P

  5. The date seems to start on Wednesday the 22nd even though it is Thursday the 23rd. Is there away to correct this?

    1. Never mind, a moment of insight resolved the issue.

      1. Kalee says:

        Can you tell me what you did to correct this, please?

  6. Taylor Hunt says:

    I was just trying to fina a workaround for the yahoo issue the other day! Thank you for this!

  7. This is brilliant. I have been looking for a Open Source Weather API for ages. Martin you've come through with the goods again! :) Thanks!

  8. gerald says:

    Excellent but when i write this the text don't appears in French

    var weatherAPI = 'http://api.openweathermap.org/data/2.5/forecast?&lang=fr&lat='+position.coords.latitude+'&lon='+position.coords.longitude+'&callback=?'

    can you help me please

    1. Martin Angelov says:

      I am afraid I haven't tried it with different languages. If the API supports it, it should be explained in the OpenWeatherMap docs.

  9. Sid says:

    Hey Martin,

    Thanks for such a nice script. Loved it ....
    However can you add couple of features

    1. Allowing user to select between Celsius / Fahrenheit

    2. Allowing user to search / change location.

    3. If geolocation is not allowed then a searchbox for location.

    4. Option to change view to Daily data for next 5/10 (extended) days in same page

    Just my two cents ...

    :0

    Thanks

    Sid

  10. tomd says:

    Opening the page by clicking on the html doc produces nothing but a spinning wheel. The page never fully loads

    1. Martin Angelov says:

      You will have to open it through a locally running web server like XAMPP/MAMP etc (and access it through http://localhost), as browsers impose security restrictions on directly-opened local files.

  11. Tim says:

    Keeps loading forever

    Used it for http://clouddesk.koding.com

  12. James says:

    Martin, excellent script.

    I'm trying to migrate accross from the old Yahoo one which was running fine.

    I only want the current weather icon and temperature to appear, not the slider.

    Any help on this?

  13. Jermaine says:

    Could you tell me how or where to input direct location instead of having it auto generate?

  14. Michael Brooks says:

    Hi, I've taken a look at this and think it's fantastic. I would like to know though, how would I go about hard coding the coordinates? I am trying to use this for a Holiday website, so it would be good if they knew the weather forecast for that particular part of the country.

Add Comment

Add a Reply

HTML is escaped automatically. Surround code blocks with <pre></pre> for readability.
Perks:   **bold**   __italics__   [some text](http://example.com) for links