Page 1 of 1

Lazarus Pascal – Retrieve Yahoo Weather Forecast

Lazarus Pascal – Retrieve Yahoo Weather Forecast
   7

I like dabbling with Lazarus Pascal, a free IDE for developing Pascal programs for Windows, MacOS X and Linux, quite similar to Delphi.

One of my projects, would look great with a weather forecast, so I went an figured out how this works by accessing the Yahoo Weather API. This took me a little bit to figure out, but I’ve got it running now, even using https.
This week I decided to put it all in a small unit, so others can use it as well.

This unit only needs Synapse, which is free as well, and uses fcl-json, which comes with Lazarus.




Getting the Yahoo Weather Forecast

As long as Yahoo provides this service, weather conditions and forecasts can be retrieved through their Yahoo Weather API.

Once you know how this works, it’s actually pretty simple, but isn’t that with most things?

SQL Like Weather Query

As you can see on this Yahoo Weather Developer Network page, a SQL like query can be used to retrieve weather information.
If you’re familiar with SQL, then this should look familiar:

SELECT * FROM weather.forecast WHERE woeid IN (SELECT woeid FROM geo.places(1) WHERE text="nome, ak")

Here we get the forecast for Nome, AK (USA), but we let Yahoo figure out what the ID is for that town by doing a subquery:

SELECT woeid FROM geo.places(1) WHERE text="nome, ak"

The returned answer, “woeid”, will then be used in the main query to retrieve the weather forecast, where the location ID (woeid) that came from our subquery, matches the location ID (woeid) of the weather.forecast table.

Weather Forecast: JSON or XML?

We also see that information can be retrieved as JSON data or as XML data. I choose JSON in my unit, since JSON works smooth and has little overhead. XML has caused me enough headaches in the past, so I’m trying to avoid that one.

Making a URL to retrieve Yahoo Weather Forecasts

Let’s look at the URL, for retrieving weather conditions and forecasts, you can find it at the bottom of this Yahoo Weather Developer page:


https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22nome%2C
%20ak%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys

Now keep in mind what we were doing:

  • Get the weather for “Nome, AK” (I’ve never been there, but that’s irrelevant I suppose),
  • Make sure we get it in JSON format

 

The base of the URL is:

https://query.yahooapis.com/v1/public/yql?q=...

Followed by the SQL query that we just looked at, however it’s written in HTML save character codes (called URL Encoding, or Percent-encoding, ie. a space becomes %20, which is hex 20, which is decimal 32, which is the ASCII code for a space).

select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22nome%2C%20ak%22)...

See the text%3D%22nome%2C%20ak%22 part?
Translated in normal characters it says: test=”nome, ak”

So this is where we can pass the text to determine location. The space just before “ak” is actually not even needed, and upper or lowercase doesn’t matter either. If your location however has spaces or special characters in it, then you’d need to URL encode these, so in my unit I added a function to that automatically.

Note though that sometimes you’ll need to provide more information when it comes to passing the location.

For example, when looking for “Osceola, WI”. If I’d provide “osceola,usa”, then you’d get into trouble, since there are multiple towns called Osceola in the USA. The query would only return the first Osceola it found, which happens to be “osecola,fl” – in Florida. So don’t forget to add the state where application: “osceola,wi,usa” (“usa” would not even be needed since it’s in the US).

For cities in different countries, just the city name and country name, is often sufficient.
For example “breda,netherlands”.
With some testing I discovered that you might be able to get away with writing the country name in it’s native notation, so in the previous example: “breda,nederland”. Which works as well.

This part is followed by defining the output format (JSON!).

&format=json...

The rest is just some additional info we need to add, but you don’t need to worry about that:

&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys

So now we know how to build the query.
I’m just explaining it here, so you can read through my code and understand better, which is what.

The Yahoo Weather Forecast Unit

Like I said before, I made a little Pascal unit which handles this just nicely – keep in mind that there is always room for improvement.

Yahoo Weather Forecast in a Record

To get a readable result, I decided to have the main function return a record holding all the data.

The JSON data will automatically be taken apart and stored in this record (TYahooForecast). Since Yahoo includes the forecast for 10 days, I create another record format which can hold the specifics for one day (see the forcast in the TYahooForecast below).


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  TDayForcast = record
    code:string;
    date:string;
    day:string;
    high:string;
    low:string;
    text:string;
  end;

  TYahooForecast = record
    success:boolean;
    distance:string;
    pressure:string;
    speed:string;
    temperature:string;
    location_city:string;
    location_country:string;
    location_region:string;
    wind_chill:string;
    wind_direction:string;
    wind_speed:string;
    atmosphere_humidity:string;
    atmosphere_pressure:string;
    atmosphere_rising:string;
    atmosphere_visibility:string;
    astronomy_sunrise:string;
    astronomy_sunset:string;
    location_latitude:string;
    location_longitude:string;
    last_updated:string;
    current_code:string;
    current_date:string;
    current_temp:string;
    current_text:string;
    forecast: array [0..9] of TDayForcast;
  end;

To get the forecast, we simply call:


1
2
3
4
5
6
7
8
// using defaults (in Celsius)
tmpData := GetYahooForecast('nome, ak');

// forcing Fahrenheit
tmpData := GetYahooForecast('nome, ak','f');

// forcing Celsius
tmpData := GetYahooForecast('nome, ak','c');

You can add special characters and spaces in the location name, URL Encoding will be handled automatically.

The first thing to check in this example is if everything went OK. You can read that in the “success” field of the record.


1
2
3
4
5
6
7
8
9
10
11
  if tmpData.success then
    begin
      with tmpData do
        begin
          memo1.lines.add('Distance units: '+distance);
          memo1.lines.add('Pressure units: '+pressure);
          ...
        end;
    end
  else
    // FAILED RETRIEVING DATA

As you can see, it’s easy to access the results.

One thing to keep in mind, is the 10 day forecast, which you access like so:


1
2
3
4
5
6
7
8
9
for Counter:=0 to 9 do
  begin
    memo1.lines.add('Code: '+tmpdata.forecast[Counter].code);
    memo1.lines.add('Date: '+tmpdata.forecast[Counter].date);
    memo1.lines.add('Day: ' +tmpdata.forecast[Counter].day);
    memo1.lines.add('High: '+tmpdata.forecast[Counter].high);
    memo1.lines.add('Low: ' +tmpdata.forecast[Counter].low);
    memo1.lines.add('Text: '+tmpdata.forecast[Counter].text);
  end;

The Unit

This is the code for the unit I created.
Note that it uses Synapse, which can be dowloaded for free. You do not need to install it – in fact, on my Mac it will not even install. Simply unzip the downloaded file and copy all the files from “/source/lib” into your project directory.

Update: HTTPS and Windows users … 

Since the HTTPS protocol is being used, some sorts of SSL support needs to be available.
Now under MacOS X and Linux, OpenSSL is readily available and used by Synapse. Since I developed this on a Mac, I had not noticed that this could be an issue for Windows users – Thanks to Joseph Kaminchick for catching this! 

So for Windows I have included precompiled OpenSSL tools in the ZIP (libeay32.dll, openssl.exe en ssleay32.dll).

Keep in mind that the included library assumes you’re compiling a 32 bit application and have not been tested with 64 bit applications.

 

You can copy and paste the code, or download it (includes a small example application):

DOWNLOAD - LazarusYahooWeatherUnit 

Platform: Windows, Mac OS X, Linux
Filename: LazarusYahooWeatherUnit.zip
Version: 1.1
Size: 1.4 MiB
Date: December 17, 2016
 Download Now 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
unit YahooWeather;

{  Unit to retrieve Yahoo Weather Forecast

   (C)2015 Hans Luijten, //www.tweaking4all.com
   Version 1.0

   Uses:  Synapse (http://synapse.ararat.cz/,
          fpjson  (Comes with Lazarus)
          openssl (part of synapse, weather is retrieved with https://)

   Common use:
          GetYahooForecast(location);

          Retrieves current weather and weather forecast, and returns this in a record for easy access.
}


{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, httpsend, ssl_openssl, fpjson, jsonparser;

type
  TDayForcast = record
    code:string;
    date:string;
    day:string;
    high:string;
    low:string;
    text:string;
  end;

  TYahooForecast = record
    success:boolean;
    distance:string;
    pressure:string;
    speed:string;
    temperature:string;
    location_city:string;
    location_country:string;
    location_region:string;
    wind_chill:string;
    wind_direction:string;
    wind_speed:string;
    atmosphere_humidity:string;
    atmosphere_pressure:string;
    atmosphere_rising:string;
    atmosphere_visibility:string;
    astronomy_sunrise:string;
    astronomy_sunset:string;
    location_latitude:string;
    location_longitude:string;
    last_updated:string;
    current_code:string;
    current_date:string;
    current_temp:string;
    current_text:string;
    forecast: array [0..9] of TDayForcast;
  end;

function GetYahooWeatherData(Location:string; AsJSON:boolean =true; TemperatureUnits:string = 'c'):string;
function GetYahooForecast(Location:string; TemperatureUnits:string = 'c'):TYahooForecast;
function YahooURLEncode(s: string): string;

implementation

{
  URLEncode

  Encodes string to be suitable as an URL.

  Example: myencodeURL:=YahooURLEncode(myURL);
}

function YahooURLEncode(s: string): string;
var
  i: integer;
  source: PAnsiChar;
begin
  result := '';
  source := pansichar(s);
  for i := 1 to length(source) do
    if not (source[i - 1] in ['A'..'Z', 'a'..'z', '0'..'9', '-', '_', '~', '.', ':', '/']) then
      result := result + '%' + inttohex(ord(source[i - 1]), 2)
    else
      result := result + source[i - 1];
end;

{ Retrieve Yahoo Weather with forcast and place it in a record for easy access

  MyYahooForcast := GetYahooForecast('town,country',true,'c')
}

function GetYahooForecast(Location:string; TemperatureUnits:string = 'c'):TYahooForecast;
var JSONData:TJSONData;
    RawData:string;
    tmpData:TYahooForecast;
    Counter:integer;
begin
  RawData := GetYahooWeatherData(Location,true,TemperatureUnits);

  Counter:=0;
  while ((RawData='') or (pos('ERROR',RawData)>0)) and (Counter<3) do
    begin
      RawData := GetYahooWeatherData(Location,true,TemperatureUnits);
      inc(Counter);
    end;

  if ((RawData='') or (pos('ERROR',RawData)>0)) then
    tmpData.success:=false
  else
    begin
      JSONData := GetJSON(RawData);

      with tmpdata do
        begin
          success:=true;

          distance:=JSONData.FindPath('query.results.channel.units.distance').AsString;
          pressure:=JSONData.FindPath('query.results.channel.units.pressure').AsString;
          speed:=JSONData.FindPath('query.results.channel.units.speed').AsString;
          temperature:=JSONData.FindPath('query.results.channel.units.temperature').AsString;

          location_city:=JSONData.FindPath('query.results.channel.location.city').AsString;
          location_country:=JSONData.FindPath('query.results.channel.location.country').AsString;
          location_region:=JSONData.FindPath('query.results.channel.location.region').AsString;

          wind_chill:=JSONData.FindPath('query.results.channel.wind.chill').AsString;
          wind_direction:=JSONData.FindPath('query.results.channel.wind.direction').AsString;
          wind_speed:=JSONData.FindPath('query.results.channel.wind.speed').AsString;

          atmosphere_humidity:=JSONData.FindPath('query.results.channel.atmosphere.humidity').AsString;
          atmosphere_pressure:=JSONData.FindPath('query.results.channel.atmosphere.pressure').AsString;
          atmosphere_rising:=JSONData.FindPath('query.results.channel.atmosphere.rising').AsString;
          atmosphere_visibility:=JSONData.FindPath('query.results.channel.atmosphere.visibility').AsString;

          astronomy_sunrise:=JSONData.FindPath('query.results.channel.astronomy.sunrise').AsString;
          astronomy_sunset:=JSONData.FindPath('query.results.channel.astronomy.sunset').AsString;

          location_latitude:=JSONData.FindPath('query.results.channel.item.lat').AsString;
          location_longitude:=JSONData.FindPath('query.results.channel.item.long').AsString;

          last_updated:=JSONData.FindPath('query.results.channel.item.pubDate').AsString;

          current_code:=JSONData.FindPath('query.results.channel.item.condition.code').AsString;
          current_date:=JSONData.FindPath('query.results.channel.item.condition.date').AsString;
          current_temp:=JSONData.FindPath('query.results.channel.item.condition.temp').AsString;
          current_text:=JSONData.FindPath('query.results.channel.item.condition.text').AsString;

          for Counter:=0 to 9 do
            begin
              with tmpData.forecast[Counter] do
                begin
                  code:=JSONData.FindPath('query.results.channel.item.forecast['+IntToStr(Counter)+'].code').AsString;
                  date:=JSONData.FindPath('query.results.channel.item.forecast['+IntToStr(Counter)+'].date').AsString;
                  day:=JSONData.FindPath('query.results.channel.item.forecast['+IntToStr(Counter)+'].day').AsString;
                  high:=JSONData.FindPath('query.results.channel.item.forecast['+IntToStr(Counter)+'].high').AsString;
                  low:=JSONData.FindPath('query.results.channel.item.forecast['+IntToStr(Counter)+'].low').AsString;
                  text:=JSONData.FindPath('query.results.channel.item.forecast['+IntToStr(Counter)+'].text').AsString;
                end;
            end;
       end;
    end;

  GetYahooForecast := tmpData;
end;

{ Retrieve Yahoo Weather information

  myString := GetYahooWeatherData('town,country',true,'c');

  Location: typically use town,country, for example: 'osceola,wi.usa', 'amsterdam,netherlands', 'breda,nederland'
  AsJSON  : return as a JSON string if TRUE, otherwise in XML format (JSON default)
  TemperatureUnits: 'c' for Celcius, and 'f' for Fahrentheit         (C default)
}

function GetYahooWeatherData(Location:string; AsJSON:boolean =true; TemperatureUnits:string = 'c'):string;
var HTTPSender : THTTPSend;
    tmpData : TStringList;
    resultString, resultFormat: String;
begin
  if Location='' then
    GetYahooWeatherData:='ERROR: Missing Location'
  else
    begin
      if AsJSON then
        resultFormat := 'json'
      else
        resultFormat := 'xml';

      tmpData    := TStringList.Create;
      HTTPSender := THTTPSend.Create;

      try
        HTTPSender.HTTPMethod('GET',
                              'https://query.yahooapis.com/v1/public/yql?'+
                                'q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20('+                           // get forecast
                                  'select%20woeid%20from%20geo.places(1)%20where%20text%3D%22'+YahooURLEncode(Location)+'%22)'+              // find location based on string
                                '%20and%20u%3D%22'+TemperatureUnits+'%22'+                                                   // force units (c or f)
                                '&format='+resultFormat+'&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys');

        if (HTTPSender.ResultCode >= 100) and (HTTPSender.ResultCode<=299) then
          tmpData.LoadFromStream(HTTPSender.Document)
      finally
        HTTPSender.Free;
      end;

      if tmpData.Text='' then
        resultString:='ERROR: Unable to retrieve data'
      else
        resultString:=tmpData.Text;

      tmpData.Free;

      GetYahooWeatherData := resultString;
    end;
end;

end.

Extra Info: Yahoo Weather Codes

Yahoo Weather also returns a code for each forecast and the current conditions. This code is used to verbalize the conditions.

Here is a list of what these codes mean, which can be practical if you’d like to translate the condition text. Obviously, for English speaking users, this list is not relevant since Yahoo already returns a similar text in the “text” (for each day) and “current_text” (for today) fields.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
const YahooWeatherCondition : array[0..48] of string = ('tornado',
                                                        'tropical storm',
                                                        'hurricane',
                                                        'severe thunderstorms',
                                                        'thunderstorms',
                                                        'mixed rain and snow',
                                                        'mixed rain and sleet',
                                                        'mixed snow and sleet',
                                                        'freezing drizzle',
                                                        'drizzle',
                                                        'freezing rain',
                                                        'showers',
                                                        'showers',
                                                        'snow flurries',
                                                        'light snow showers',
                                                        'blowing snow',
                                                        'snow',
                                                        'hail',
                                                        'sleet',
                                                        'dust',
                                                        'foggy',
                                                        'haze',
                                                        'smoky',
                                                        'blustery',
                                                        'windy',
                                                        'cold',
                                                        'cloudy',
                                                        'mostly cloudy (night)',
                                                        'mostly cloudy (day)',
                                                        'partly cloudy (night)',
                                                        'partly cloudy (day)',
                                                        'clear (night)',
                                                        'sunny',
                                                        'fair (night)',
                                                        'fair (day)',
                                                        'mixed rain and hail',
                                                        'hot',
                                                        'isolated thunderstorms',
                                                        'scattered thunderstorms',
                                                        'scattered thunderstorms',
                                                        'scattered showers',
                                                        'heavy snow',
                                                        'scattered snow showers',
                                                        'heavy snow',
                                                        'partly cloudy',
                                                        'thundershowers',
                                                        'snow showers',
                                                        'isolated thundershowers',
                                                        'not available');

With a simple procedure you can convert the code to text, assuming you defined a variable or constant array like the one above.
Codes > 48 will result in a “not available”.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
function WeatherCode2String(code:string):string;
var intCode:integer;
begin
  try
    intCode:=StrToInt(code)
  except
    intCode:=3200
  end;

  if (intCode<0) or (intCode>47) then
    WeatherCode2String:= YahooWeatherCondition[48]
  else
    WeatherCode2String:= YahooWeatherCondition[intCode]
end;

 

Donation options


Donations are very much appreciated, but not required. Donations will be used for web-hosting expenses, project hardware or a motivational boost (a drink or snack). Thank you very much for those have donated already! It's truly AwEsOmE to see that folks like our articles and small applications.

Comments


There are 7 comments. You can read them below.
You can post your own comments by using the form below, or reply to existing comments by using the "Reply" button.

  • Aug 30, 2016 - 11:35 PM - JP Zhang Comment Link

    Good to see your update! Have a great week, Hans :)

    Reply

    JP Zhang

  • Dec 13, 2016 - 3:44 AM - Joseph Kaminchick Comment Link

    Hello,

    Very impressive. However, I have downloaded the sample project, compiled and ran it with the latest version of Lazarus both under Linux and windows 7 and it does not work. In Linux no run time error is shown. In windows 7 I am getting a “Heap Error” (I can mail you a screenshot).

    Can you please help,

    thank’s

    Reply

    Joseph Kaminchick

    • Dec 13, 2016 - 9:37 AM - hans - Author: Comment Link

      Hi Joseph!

      I’m at work right now, but I’ll try the sample project again with the latest Lazarus on both Windows and Linux.

      Reply

      hans

    • Dec 17, 2016 - 10:01 AM - hans - Author: Comment Link

      Hi Joseph!

      Just tried it with Lazarus 1.6.2 under Windows (I wrote the unit under MacOS) and can reproduce the error.
      I’ll do my best to find a fix!

      Reply

      hans

      • Dec 17, 2016 - 10:52 AM - hans - Author: Comment Link

        Found the problem ..

        Seems that OpenSSL does not standard come with Windows (unlike Mac/Linux it seems).
        After downloading a precompiled set of OpenSSL dll’s (link), the program works.

        I used this specific version and copied the 3 dll files into my project folder. You’d need to distribute those with your application, when developing for Windows. Note: Even though I tested this with Windows 10 64 bit, my Lazarus is the 32 bit version, so the selected dll is 32 bit as well. I have not tested this with the 64bit version of Lazarus or the dll’s.

        I’ll update the zip file to include these 3 DLL’s. Thanks for catching this! 

        Reply

        hans

        • Dec 17, 2016 - 11:04 AM - hans - Author: Comment Link

          Updated the ZIP.

          The DLL’s are only for Windows users and included in the ZIP file.
          It is recommended to try to use the most recent version which you can find here.

          The files libeay32.dll, openssl.exe and ssleay32.dll should be distributed with your application.

          Reply

          hans



Your Comment …

Friendly request to not post large files here (like source codes, log files or config files). Please use the Forum for that purpose.

Please share:
*
*
Notify me about new comments (email).
       You can also use your RSS reader to track comments.


Tweaking4All uses the free Gravatar service for Avatar display.
Tweaking4All will never share your email address with others.