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.
Ad Blocking Detected Please consider disabling your ad blocker for our website.
We rely on these ads to be able to run our website.
You can of course support us in other ways (see Support Us on the left).
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 - Lazarus Pascal YahooWeather Unit
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. |
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; |
Comments
There are 9 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.
Good to see your update! Have a great week, Hans :)
JP Zhang
Thanks JP
hans
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
Joseph Kaminchick
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.
hans
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!
hans
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!
hans
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.
hans
Hi
The program works flawlessly on a fresh copy of Ubuntu 17.10, but I first had to install libssl-dev. I used Synaptic;
in a terminal would work.
There is no warning about the missing ssl development package when running the program, however nothing will be displayed and the HTTPSender.ResultCode (in the GetYahooWeatherData function in unit YahooWeather) will be 500.
The program works with the latest version of Ararat Synapse (40.1) obtained by downloading the snapshot at https://sourceforge.net/p/synalist/code/HEAD/tree/trunk/.
Thank you very much for this.
Michel
Hi Michel!
Thanks for the tip! I’m not usually working on a Linux PC, and I believe you’re right about having to install libssl. Windows users may need something like this as well.
Thanks again for the very useful tip!
hans