function [data,err] = yweather(location,unit)
%YWEATHER Get location specific weather data from Yahoo! Weather.
%
% YWEATHER(LOCATION) queries the Yahoo! Weather RSS feed for the specified
% location, and returns a struct containing the queried information.
%
% LOCATION can be a (U.S.) ZIP Code or a Location ID, and it must be a
% string. To determine Location ID(s), visit http://weather.yahoo.com/ and
% browse or search for the relevant location(s).
%
% The second argument UNIT is optional, and can be either 'f' for
% English or 'c' for metric units. Yahoo! Weather's default value for UNIT
% is 'f'.
%
% For more information about the RSS feeds and the data contained within
% them, visit http://developer.yahoo.com/weather/.
%
% Data is acquired in XML format, after which it is converted into a struct
% using the xml_parse function. This function is available in the XML
% Toolbox by Marc Molinari on the MATLAB Central File Exchange (File ID
% 4278). The URL to it is:
% http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=4278&objectType=file
%
% EXAMPLES:
%
% Example 1: Using a ZIP Code.
% yweather('10001') returns a 1x1 struct having fields (among others):
%
% yweather_location.city: 'New York'
% yweather_atmosphere.humidity: 50
% yweather_astronomy.sunrise: '6:06 am'
% item.yweather_condition.temp: 35
% item.yweather_condition.date: 'Thu, 16 Mar 2006 2:51 am EST'
%
% Example 2: Using a Location ID and (optional) unit.
% yweather('UKXX0085','c') returns a 1x1 struct.
%
% Example 3: Using a cell array of three locations.
% cellfun(@yweather,{'90028';'14850';'CAXX0301'}) returns a 3x1 struct.
%
% Example 4: Using a char array of two ZIP Codes.
% cellfun(@yweather,cellstr(['56711';'33040'])) returns a 2x1 struct.
%
% Example 5: Using a cell array of three locations, in degrees Celsius.
% locations={'98381';'USCA0715';'04652'};
% units=repmat({'c'},size(locations));
% cellfun(@yweather,locations,units) returns a 3x1 struct.
%
% REMARKS:
%
% - This function does not include any caching features, so conservative
% use of the service, perhaps no more than once per hour
% per location, is warranted. Refer to the corresponding
% 'ttl' field values and documentation.
% - As the feed layout is updated by Yahoo! over time, the layout of
% the output struct should automatically be correspondingly updated.
% Unexpected changes to the feed may however prevent this function from
% working predictably.
% - This function has been tested with version 2005-04-20 of the XML
% Toolbox.
%
% VERSION: 20060321
% MATLAB VERSION: 7.1.0.246 (R14) Service Pack 3
%
% See also URLREAD, REGEXP, REGEXPREP, CELLFUN, STRUCTFUN.
%{
VERSION HISTORY:
20060317: - Original version by A. Belani
http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=10414&objectType=file
20060321: - Modified slightly to be used with a GUI.
KEYWORDS:
atmosphere, climate, meteorology, temperature, weather, yahoo
%}
%--------------------------------------------------------------------------
%% Declare URL and parameters
% Error handling flag
err = 0; % If this flag stays zero then no error exists.
%Declare url
url='http://xml.weather.yahoo.com/forecastrss';
%Conditionally declare parameters
switch nargin
case 1
params={'p',location};
case 2
params={'p',location,'u',unit};
end
clear unit
%--------------------------------------------------------------------------
%% Resiliently read URL
tries_max=3;
wait_time_base=.75;
for tries=1:tries_max;
[data,status]=urlread(url,'get',params);
if status==1
break
end
if tries==tries_max
err = 1; % This means that unable to connect to internet.
return
end
pause(tries*wait_time_base)
end
clear tries* status wait_time_base ...
url params
%--------------------------------------------------------------------------
%% Check for invalid returned data
if ~isempty(strfind(data,'<title>City not found</title>'))
err = 2; % This mean the city is not found.
return
end
clear location
%--------------------------------------------------------------------------
%% Convert yweather code to XML (for convenient parsing to struct)
expr='<yweather:(?<category>\w+) (?<keys_values>[^>]+)/>';
data=regexprep(data,expr,'<yweather:$1>$2</yweather:$1>');
expr='<yweather:\w+>(?<keys_values>[^>]+)</yweather:\w+>';
dc1=regexp(data,expr,'match');
expr='(?<key>\w+)="(?<value>[^"]+)" ';
dc2=regexprep(dc1,expr,'<$1>$2</$1>');
data=regexprep(data,dc1,dc2);
clear expr dc*
%--------------------------------------------------------------------------
%% Parse and store <description> CDATA value correctly
expr='<description><!\[CDATA\[(?<description>.+)]]></description>';
description=regexp(data,expr,'names','once');
clear expr
description=description.description;
%--------------------------------------------------------------------------
%% Preprocess XML for parsing
%Rename <item> tag to something else (<itemdata>) to prevent parsing error
data=regexprep(data,'<(?<slash>[/]?)item>','<$1itemdata>');
%Replace colon in relevant (yweather* and geo*) tags to underscore for
%compatibility with struct field naming conventions
expr='<(?<slash>[/]?)(?<tag_left_part>\w+):(?<tag_right_part>\w+)>';
data=regexprep(data,expr,'<$1$2_$3>');
clear expr
%Convert multiple <yweather_forecast> sets into a single set
data=regexprep(data,'</yweather_forecast>\n{1}<yweather_forecast>','');
%% Parse XML to struct
%Disable warning, and parse
wstate=warning('off','all');
data=xml_parse(data);
warning(wstate);
clear wstate
%--------------------------------------------------------------------------
%% Postprocess struct
%Simplify struct
data=data.channel;
%Rename <itemdata> back to <item> (in struct)
data.item=data.itemdata;
data=rmfield(data,'itemdata');
%Store previously parsed <description> CDATA value correctly in struct
data.item.description=description;
clear description
%--------------------------------------------------------------------------
%% Convert numbers from strings to doubles
% Comment this cell to prevent numbers from being converted to doubles
data=structfun(@cnsd,data,'UniformOutput',false);
%**************************************************************************
%% Subfunction to convert numbers from strings to doubles
function[field]=cnsd(field)
if ischar(field) && ~isnan(str2double(field))
field=str2double(field);
elseif isstruct(field)
for i=1:numel(field)
field(i)=structfun(@cnsd,field(i),'UniformOutput',false);
end
end
%**************************************************************************