Weather Forecast Webapp revisado

Hace un año, publiqué un tutorial sobre la creación de una aplicación web de pronóstico del tiempo, impulsada por las API de Yahoo y las capacidades de geolocalización integradas del navegador. Sin embargo, recientemente Yahoo descontinuó estas API gratuitas, por lo que hoy vamos a convertir la aplicación web a un servicio diferente:OpenWeatherMap.

La API de OpenWeatherMap

OpenWeatherMap no solo es de uso gratuito, sino que también devuelve el pronóstico para los próximos cinco días y hace con una sola solicitud de API lo que Yahoo solo podría hacer con dos. La API toma directamente un conjunto de coordenadas geográficas y devuelve datos meteorológicos (no es necesario encontrar primero la ciudad). Estas características hacen que su uso sea sencillo y lamento no haber conocido este servicio antes. Aquí hay una respuesta de ejemplo:

{
    "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..

    ]
}

Una sola llamada a la API devuelve información geográfica, el nombre de la ciudad, el código del país y el pronóstico del tiempo detallado. Las predicciones meteorológicas se devuelven en el list propiedad como una matriz y están separados por tres horas. En nuestro código, tendremos que recorrer esta lista y presentar el pronóstico como una serie de diapositivas. La buena noticia es que mucho del trabajo que hicimos en el tutorial anterior se puede reutilizar.

Qué debe cambiarse

No comenzaremos desde cero:reutilizaremos el HTML y el diseño del último tutorial. En la parte de HTML, todo es casi igual que en el original, con la excepción de que actualicé a la última versión de jQuery e importé la biblioteca de fecha/hora moment.js (consejo rápido) que usaremos para presentar el hora de las previsiones.

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>

Sin embargo, lo que debe reescribirse es nuestro código JavaScript. OpenWeatherMap es más simple de usar y toma las coordenadas de la API de geolocalización directamente, por lo que eliminé gran parte del código antiguo. Otro beneficio es que no es necesario registrarse para obtener una clave API en OpenWeatherMap, lo que significa que podemos saltar directamente a la fuente:

activos/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);
    }

});

La mayoría de los cambios son para el locationSuccess() función, donde hacemos una solicitud a la API y llamamos a addWeather() . Este último también necesitaba algunos cambios, por lo que utiliza el código de icono contenido en los datos meteorológicos para presentar la imagen correcta de la carpeta assets/img/icons. Consulte la lista de iconos (versiones diurnas y nocturnas) y códigos meteorológicos en los documentos de OpenWeatherMap.

Otra cosa que vale la pena señalar es la forma en que estoy usando el localStorage persistente objeto para almacenar en caché el resultado de la API durante 30 minutos, lo que limita la cantidad de solicitudes que van a OpenWeatherMap, para que todos puedan obtener su parte justa.

¡Con esto nuestra aplicación web Weather está lista! ¿Tiene alguna pregunta? Haga clic en la sección de comentarios a continuación.