Get weather and use LINQ with NativeScript

In this tutorial I will show you how to load weather information from OpenWeatherMap and prepare the data with LINQ style extensions.

 

OpenWeatherMap provides a powerful REST API. If you do not need more than 60 requests a minute, the service is FREE.

To use it, you have to create an account

openweathermap_signup

and obtain an API key:

openweathermap_api_keys

App requirements

To communicate with the API, we first need the nativescript-apiclient plugin:

tns plugin add nativescript-apiclient

To prepare the JSON reponses to handy objects, we also need the LINQ extension nativescript-enumerable:

tns plugin add nativescript-enumerable

And we need TypeScript support, of course:

tns plugin add nativescript-dev-typescript

You can include the plugins by using the following code:

import ApiClient = require('nativescript-apiclient');
import Enumerable = require('nativescript-enumerable');

That’s all … so let’s start loading data …

Forecast for 5 days

The endpoint http://api.openweathermap.org/data/2.5/forecast?zip=<ZIP-CODE>,<COUNTRY>&APPID=<APP-KEY> provides a 5 day forecast by defining the zip code of a city. Each day stores the expected weather in 3 hour steps (00:00, 03:00, 06:0018:00 and 21:00).

First we should define the TypeScript interface for an entry …

An entry:

interface IForecastEntry {
    /**
     * The temperature data.
     */
    temperature: {
        /**
         * Maximum temperature, in Kelvin
         */
        max: number;
 
        /**
         * Minimum temperature, in Kelvin
         */
        min: number;
 
        /**
         * The value expected value, in Kelvin
         */
        value: number;
    },
 
    /**
     * The UTC timestamp the entry is for
     */
    time: Date;
 
    /**
     * The expected weather(s) as IDs.
     */
    weather: number[];
}

Now we can start to setup the client:

var apiKey = '<YOU-API-KEY>';
var zipCode = '52222';
var country = 'de';
 
// initialize
var client = ApiClient.newClient({
    baseUrl: 'http://api.openweathermap.org/data/2.5/forecast',
});
 
// setup
client.ok((result) => {
    // handle the reponse
}).unauthorized((result) => {
    console.log('Invalid API key!');
}).error((ctx) => {
    console.log('Client ERROR: ' + ctx.error);
});
 
// do the request
client.get({
    params:  {
        'APPID': apiKey,
        'zip': zipcode + ',' + country,
    },
});

In the “OK” section

client.ok((result) => {
    // handle the reponse
})

we will define the logic to handle the JSON response.

The service returns data like this:

{
  "city": {
    "id": 2826595,
    "name": "Stolberg",
    "coord": {
      "lon": 6.23333,
      "lat": 50.76667
    },
    "country": "DE",
    "population": 0,
    "sys": { "population": 0 }
  },
  "cod": "200",
  "message": 0.0381,
  "cnt": 38,
  "list": [
    {
      "dt": 1469955600,
      "main": {
        "temp": 292.37,
        "temp_min": 288.857,
        "temp_max": 292.37,
        "pressure": 976.87,
        "sea_level": 1027.68,
        "grnd_level": 976.87,
        "humidity": 94,
        "temp_kf": 3.52
      },
      "weather": [
        {
          "id": 500,
          "main": "Rain",
          "description": "light rain",
          "icon": "10d"
        }
      ],
      "clouds": { "all": 76 },
      "wind": {
        "speed": 3.17,
        "deg": 290.501
      },
      "rain": { "3h": 0.08 },
      "sys": { "pod": "d" },
      "dt_txt": "2016-07-31 09:00:00"
    }
 
    // ...
  ]
}

As you can see, the entries that are stored in the list property are not compatible with the IForecastEntry interface that we have defined … and this is why we need LINQ here!

The first thing we have to do is to create an object from the result:

var respObj = result.getJSON();

The next step is to create a “LINQ sequence” from the array of list property:

var seq = Enumerable.fromArray(respObj.list);

With the select() method of the new sequence, we can project each element to a new object or value:

var entries = seq.select((x) => {
    // IForecastEntry
    return {
        temperature: {
            max: x.main.temp_max,
            min: x.main.temp_min,
            value: x.main.temp,
        },
 
        time: new Date(x.dt * 1000),
 
        // extract the IDs
        // of the weather types
        // from 'weather' property
        // and create a new array of numbers 
        weather: Enumerable.fromArray(x.weather)
                           .select((y) => y.id)
                           .toArray(),
    };
});

To keep sure to have a clean and sorted list by timestamp, we can use the orderBy() method:

var orderedEntries = entries.orderBy((x) => {
    return x.time;
});

And it is a good idea to group them by day:

var groupedEntries = orderedEntries.groupBy((x) => {
    // we only want the date part
    var d = new Date(x.time.getYear(),
                     x.time.getMonth(),
                     x.time.getDate());
 
    return d.getTime();  // use UNIX timestamp
                         // to group
});

To iterate the groups and their elements, we can make use of the each() method:

groupedEntries.each((grp: Enumerable.IGrouping<number, IForecastEntry>) => {
    // 'grp.key' contains the
    // value we haved grouped by
    var day = new Date(grp.key);
 
    // iterate over the items
    // of the current group
    grp.each((grpItem: IForecastEntry) => {
        //TODO
    });
});

But one of the powerful “features” of LINQ is that most methods are “chainable”.

So we can write all the steps we made into “one line”:

Enumerable
     // make sequence from array
    .fromArray(result.getJSON().list)
     // convert to 'IForecastEntry' objects
    .select((x) => {
                temperature: {
                    max: x.main.temp_max,
                    min: x.main.temp_min,
                    value: x.main.temp,
                },
                time: new Date(x.dt * 1000),
                weather: Enumerable.fromArray(x.weather)
                                   .select((y) => y.id)
                                   .toArray(),
            })
     // order by timestamp
    .orderBy((x) => x.time)
     // group by day
    .groupBy((x) => (new Date(x.time.getYear(),
                              x.time.getMonth(),
                              x.time.getDate())).getTime())
     // iterate over groups
     // and their data
    .each((grp) => {
              var day = new Date(grp.key);
 
              grp.each((grpItem: IForecastEntry) => {
                  //TODO
              });
          });

OK … what have we done at all?

  • We loaded the data from API
  • Converted them into own handy objects
  • Ordered them by time
  • And grouped them by day

And in a way that is as simple as writing SQL queries!

Have a look at OpenWeatherMap.ts (Service class) of the demo app to get more information about handling data using LINQ like statements in NativeScript.

Leave a Reply

Your email address will not be published. Required fields are marked *