Adding Local Weather Conditions To Your App (Part 2/2: Accessing Google’s XML Weather API)

  • Twitter
  • Facebook
  • Digg
  • Reddit
  • StumbleUpon
  • del.icio.us
  • Google Bookmarks
September 29th, 2010 Posted by: (ELC) - posted under:Tutorials


In this Part 2 of ‘Adding Local Weather Conditions To Your App’, I’ll show you how to quickly add current temp, conditions, and today’s high / low temperature to your app.

If you’re lucky enough to already have the user’s zipcode or city and state, this should go very quickly for you. Otherwise, check out Part 1 (Integrating CoreLocation).

Let’s get started.

There are a handful of solid XML Weather APIs out there. The best one I’ve seen so far is Wunderground’s (it’s extremely well documented) but for the purposes of this tutorial, I decided to use Google’s “super secret” Weather API. It’s incredibly simple and should take care of all your basic weather needs. Though if you’re planning on releasing a production App, be sure to pick a public API and check out their TOS (some require API keys, or fees for production use). Here’s a good list of Weather APIs

Let’s look at some example calls to Google:

http://www.google.com/ig/api?weather=01451

http://www.google.com/ig/api?weather=nyc

http://www.google.com/ig/api?weather=Portland,OR

http://www.google.com/ig/api?weather=Jamaica

As you can see, there’s a bit of flexibility in how you can query the service. The one piece it’s lacking is querying by latitude and longitude. Lucky for you, I’ll show you how to use MKReverseGeocoder to determine your user’s City/State, which you can then plug right into the GET request. First, let’s take a quick look at the XML that comes back from the API:

<?xml version="1.0"?>
  <xml_api_reply version="1">
    <weather module_id="0" tab_id="0" mobile_row="0" mobile_zipped="1" row="0" section="0">
      <forecast_information>
        <city data="Portland, OR"/>
        <postal_code data="97217"/>
        <latitude_e6 data=""/>
        <longitude_e6 data=""/>
        <forecast_date data="2010-09-20"/>
        <current_date_time data="2010-09-20 23:46:50 +0000"/>
        <unit_system data="US"/>
      </forecast_information>
      <current_conditions>
        <condition data="Cloudy"/>
        <temp_f data="64"/>
        <temp_c data="18"/>
        <humidity data="Humidity: 55%"/>
        <icon data="/ig/images/weather/cloudy.gif"/>
        <wind_condition data="Wind: SW at 8 mph"/>
      </current_conditions>
      <forecast_conditions>
        <day_of_week data="Mon"/>
        <low data="51"/>
        <high data="66"/>
        <icon data="/ig/images/weather/partly_cloudy.gif"/>
        <condition data="Partly Cloudy"/>
      </forecast_conditions>
      <forecast_conditions>
        <day_of_week data="Tue"/>
        <low data="50"/>
        <high data="68"/>
        <icon data="/ig/images/weather/partly_cloudy.gif"/>
        <condition data="Partly Cloudy"/>
      </forecast_conditions>
      <forecast_conditions>
        <day_of_week data="Wed"/>
        <low data="53"/>
        <high data="68"/>
        <icon data="/ig/images/weather/sunny.gif"/>
        <condition data="Sunny"/>
     </forecast_conditions>
    <forecast_conditions>
      <day_of_week data="Thu"/>
      <low data="53"/>
      <high data="65"/>
      <icon data="/ig/images/weather/rain.gif"/>
      <condition data="Showers"/>
    </forecast_conditions>
  </weather>
</xml_api_reply>

All this should be pretty self explanatory. The two pieces to pay attention to here are current_conditions, and forecast_conditions. For our demo app, we’re simply going to display current temperature, conditions, a conditionsIcon, and today’s high and low temp. We’ll be able to pull all this information out of current_conditions and the first forecast_conditions (which is the forecast for today). In the interest of keeping everything organized, let’s build a class to hold our weather info.

//
//  ICB_WeatherConditions.h
//  LocalWeather
//
//  Created by Matt Tuzzolo on 9/28/10.
//  Copyright 2010 iCodeBlog. All rights reserved.
//
 
@interface ICB_WeatherConditions : NSObject {
    NSString *condition, *location;
    NSURL *conditionImageURL;
    NSInteger currentTemp,lowTemp,highTemp;
}
 
@property (nonatomic,retain) NSString *condition, *location;
@property (nonatomic,retain) NSURL *conditionImageURL;
@property (nonatomic) NSInteger currentTemp, lowTemp, highTemp;
 
- (ICB_WeatherConditions *)initWithQuery:(NSString *)query;
 
@end

In the .m we’re going to pull the data out of the XML and store it in our properties. There are several 3rd party Objective-C XML parsers. I’ve chosen to use Jonathan Wight’s TouchXML as it’s become somewhat of a standard for parsing XML on iOS. You can find it here. You’ll have to jump through a couple hoops to get TouchXML into your project. Here’s an excellent tutorial on installing TouchXML that will walk you through the whole process if you’ve never done it before.

//
//  ICB_WeatherConditions.m
//  LocalWeather
//
//  Created by Matt Tuzzolo on 9/28/10.
//  Copyright 2010 iCodeBlog. All rights reserved.
//
 
#import "ICB_WeatherConditions.h"
#import "TouchXML.h"
 
@implementation ICB_WeatherConditions
 
@synthesize currentTemp, condition, conditionImageURL, location, lowTemp, highTemp;
 
- (ICB_WeatherConditions *)initWithQuery:(NSString *)query
{
    if (self = [super init])
    {
        CXMLDocument *parser = [[[CXMLDocument alloc] initWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://www.google.com/ig/api?weather=%@", query]] options:0 error:nil] autorelease];
 
        condition         = [[[[[parser nodesForXPath:@"/xml_api_reply/weather/current_conditions/condition" error:nil] objectAtIndex:0] attributeForName:@"data"] stringValue] retain];
        location          = [[[[[parser nodesForXPath:@"/xml_api_reply/weather/forecast_information/city" error:nil] objectAtIndex:0] attributeForName:@"data"] stringValue] retain];
 
        currentTemp       = [[[[[parser nodesForXPath:@"/xml_api_reply/weather/current_conditions/temp_f" error:nil] objectAtIndex:0] attributeForName:@"data"] stringValue] integerValue];
        lowTemp           = [[[[[parser nodesForXPath:@"/xml_api_reply/weather/forecast_conditions/low" error:nil] objectAtIndex:0] attributeForName:@"data"] stringValue] integerValue];
        highTemp          = [[[[[parser nodesForXPath:@"/xml_api_reply/weather/forecast_conditions/high" error:nil] objectAtIndex:0] attributeForName:@"data"] stringValue] integerValue];
 
        conditionImageURL = [[NSURL URLWithString:[NSString stringWithFormat:@"http://www.google.com%@", [[[[parser nodesForXPath:@"/xml_api_reply/weather/current_conditions/icon" error:nil] objectAtIndex:0] attributeForName:@"data"] stringValue]]] retain];
    }
 
    return self;
}
 
- (void)dealloc {
    [conditionImageURL release];
    [condition release];
    [location release];
    [super dealloc];
}
 
@end

I’ve decided to write my own init method to handle making the request to our API. This will make for a clean implementation in our view controller.

Before we get to implementing ICB_WeatherConditions, I’ll touch briefly on location. Part 1/2 of this tutorial covered finding your user’s latitude and longitude with Core Location. Use MKReverseGeocoder to find city/state from coordinates. Start by adding both the MapKit and CoreLocation frameworks to your project.

MKReverseGeocoder works asynchronously to resolve location info; it has a delegate. In our example we set the delegate to the view controller (self). Be sure to add to your header as well. Since your view controller is now the delegate for the geocoder, make sure to implement the delegate methods for the MKReverseGeocoderDelegate protocol:

#pragma mark MKReverseGeocoder Delegate Methods
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark
{
    [geocoder release];
    [self performSelectorInBackground:@selector(showWeatherFor:) withObject:[placemark.addressDictionary objectForKey:@"ZIP"]];
}
 
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error
{
    NSLog(@"reverseGeocoder:%@ didFailWithError:%@", geocoder, error);
    [geocoder release];
}

Now we’re ready to implement ICB_WeatherConditions. I usually populate UILabels in viewDidLoad, but since we’re making API calls and downloading a remote image (the weather conditions icon), I decided to write a method to execute in the background. This lets us use synchronous requests (which a lot easier to deal with) to handle network requests without locking up the main thread. Once our network calls have finished, we call back to the main thread to update the UI accordingly.

// This will run in the background
- (void)showWeatherFor:(NSString *)query
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 
    ICB_WeatherConditions *weather = [[ICB_WeatherConditions alloc] initWithQuery:query];
 
    self.conditionsImage = [[UIImage imageWithData:[NSData dataWithContentsOfURL:weather.conditionImageURL]] retain];
 
    [self performSelectorOnMainThread:@selector(updateUI:) withObject:weather waitUntilDone:NO];
 
    [pool release];
}
 
// This happens in the main thread
- (void)updateUI:(ICB_WeatherConditions *)weather
{
    self.conditionsImageView.image = self.conditionsImage;
    [self.conditionsImage release];
 
    [self.currentTempLabel setText:[NSString stringWithFormat:@"%d", weather.currentTemp]];
    [self.highTempLabel setText:[NSString stringWithFormat:@"%d", weather.highTemp]];
    [self.lowTempLabel setText:[NSString stringWithFormat:@"%d", weather.lowTemp]];
    [self.conditionsLabel setText:weather.condition];
    [self.cityLabel setText:weather.location];
 
    [weather release];
}

Of course make sure your NIB is connected to your IBOutlets properly.

Now the final piece:

[self performSelectorInBackground:@selector(showWeatherFor:) withObject:@"97217"];

Build and Run:

And you’re done!

You can see the complete class below. I’ve also posted a demo project to github.

My name is Matt Tuzzolo (@matt_tuzzolo). I hope you found this post helpful.

//
//  LocalWeatherViewController.h
//  LocalWeather
//
//  Created by Matt Tuzzolo on 8/30/10.
//  Copyright iCodeBlog LLC 2010. All rights reserved.
//
 
#import <uikit/UIKit.h>
#import "MapKit/MapKit.h"
 
@interface LocalWeatherViewController : UIViewController <mkreverseGeocoderDelegate> {
    IBOutlet UILabel *currentTempLabel, *highTempLabel, *lowTempLabel, *conditionsLabel, *cityLabel;
    IBOutlet UIImageView *conditionsImageView;
    UIImage *conditionsImage;
}
 
@property (nonatomic,retain) IBOutlet UILabel *currentTempLabel, *highTempLabel, *lowTempLabel, *conditionsLabel, *cityLabel;
@property (nonatomic,retain) IBOutlet UIImageView *conditionsImageView;
@property (nonatomic,retain) UIImage *conditionsImage;
 
- (void)updateUI:(ICB_WeatherConditions *)weather;
 
@end

And the .m:

//
//  LocalWeatherViewController.m
//  LocalWeather
//
//  Created by Matt Tuzzolo on 8/30/10.
//  Copyright iCodeBlog LLC 2010. All rights reserved.
//
 
#import "LocalWeatherViewController.h"
#import "ICB_WeatherConditions.h"
#import "MapKit/MapKit.h"
 
@implementation LocalWeatherViewController
 
@synthesize currentTempLabel, highTempLabel, lowTempLabel, conditionsLabel, cityLabel;
@synthesize conditionsImageView;
@synthesize conditionsImage;
 
- (void)viewDidLoad {
    [super viewDidLoad];
 
    if (1) //you have coordinates but need a city
    {
        // Check out Part 1 of the tutorial to see how to find your Location with CoreLocation
        CLLocationCoordinate2D coord;
        coord.latitude = 45.574779;
        coord.longitude = -122.685366;
 
        // Geocode coordinate (normally we'd use location.coordinate here instead of coord).
        // This will get us something we can query Google's Weather API with
        MKReverseGeocoder *geocoder = [[MKReverseGeocoder alloc] initWithCoordinate:coord];
        geocoder.delegate = self;
        [geocoder start];
    }
    else // You already know your users zipcode, city, or otherwise.
    {
        // Do this in the background so we don't lock up the UI.
        [self performSelectorInBackground:@selector(showWeatherFor:) withObject:@"97217"];
    }
}
 
- (void)showWeatherFor:(NSString *)query
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 
    ICB_WeatherConditions *weather = [[ICB_WeatherConditions alloc] initWithQuery:query];
 
    [self.currentTempLabel setText:[NSString stringWithFormat:@"%d", weather.currentTemp]];
    [self.highTempLabel setText:[NSString stringWithFormat:@"%d", weather.highTemp]];
    [self.lowTempLabel setText:[NSString stringWithFormat:@"%d", weather.lowTemp]];
    [self.conditionsLabel setText:weather.condition];
    [self.cityLabel setText:weather.location];
 
    self.conditionsImageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:weather.conditionImageURL]];
 
    [weather release];
 
    [pool release];
}
 
#pragma mark MKReverseGeocoder Delegate Methods
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark
{
    [geocoder release];
    [self performSelectorInBackground:@selector(showWeatherFor:) withObject:[placemark.addressDictionary objectForKey:@"ZIP"]];
}
 
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error
{
    NSLog(@"reverseGeocoder:%@ didFailWithError:%@", geocoder, error);
    [geocoder release];
}
 
- (void)didReceiveMemoryWarning {
     [super didReceiveMemoryWarning];
}
 
- (void)viewDidUnload {
	// Release any retained subviews of the main view.
	// e.g. self.myOutlet = nil;
}
 
- (void)dealloc {
    [super dealloc];
}
 
@end
  • Duane Fields

    Good information, but people should keep in mind that being “non public”, the terms of use of this API are not clear.

  • http://icode.dreamvision-soft.com/blog/?p=86 Adding Local Weather Conditions To Your App (Part 2/2: Accessing Google’s XML Weather API) | iCode

    [...] Original post on iCodeBlog [...]

  • mtuzzolo

    Hey Duane,

    Thanks for pointing this out. I’ve updated the post with a link to some public Weather APIs should anyone be looking to put out a production app.

    Best,

    -Matt

  • http://blog.indieiphonedev.com/ Tim

    Great post! I hope Google will publish this API soon. It looks like Weather Underground does not allow use of their API in a mobile application:

    “You may not use the Wunderground Data Feed for use in a mobile application for mass distribution, even if there is no monetary cost for the mobile application. For example, you may not use the Wunderground Data Feed in a free app on the iPhone. This goes for all mobile devices including but not limited to iPhone, Android, BlackBerry, Windows Mobile, etc. Contact us through the Site to discuss using the Wunderground Data Feed in a free app.”

  • David

    Hey awesome tutorial.

    Quick question… in the xml there is something called (thursday for example)

    How would you call that in ICB_WeatherConditions.m?

    In other words what would the nodesForXPath look like?

    Thanks,
    -David

  • mtuzzolo

    Hey David,

    Thanks! So the XPath for Thursday would look like this:

    /xml_api_reply/weather/forecast_conditions[2]/low

    Looks like you can get pretty crazy with XPaths..check this link out:

    http://www.w3schools.com/XPath/xpath_syntax.asp

    -Matt

  • mtuzzolo

    Hey Tim,

    This line is pretty relevant too:

    “As with all data that you access from the Site, the data that you pull from Wunderground Data Feed may only be used by you for personal, non-commercial purposes. If you want to use that data for commercial purposes, contact us through the Site and we will supply you with rate information for commercial customers.

    -Matt

  • choise

    good point.

    for the weather informations in one of my apps i’m using “yahoo weather api” and for reverse geocoding i’m using the flickr api to receive a WOEID, this WOEID can be used for yahoo weather to get the informations.
    The Yahoo Weather API is also only for non-commercial stuff.

    As far as i know, the WOEID can now also be received from the Yahoo API itself. a few months ago, i used FLICKR, and its rocket-solid.

  • gina

    fantastic tutorial. I got everything to work properly! Thanks, it was a great learning app.
    In a moment of whacky ambition, I tried to combine the GPS of part 1, getting the latitude and longitude to use in “CLLocationCoordinate2D coord” in part 2. I am getting an error where it isn’t recognizing the call to the second delegate in my @interface declaration. I take it this is not allowed. If not, can you shed some insight on how to get around this or what the correct implementation might be. (maybe part 3 )
    thanks

    @interface MyWeatherViewController : UIViewController

  • http://thekinetik.com PRCode

    Grat tutorial, thanks!!!

  • http://developerquestion.com/forecast-weather/ Forecast weather | DeveloperQuestion.com

    [...] forecast weather I use Google api for read weather and i learn it from this post but now i want to forecast weather for 2 days does anybody have any intent about it ? Question by [...]

  • Joe Crozier

    Hey Matt,
    Quick question, how would I integrate the users location (found in part 1 of the tutorial), into the weather query.

    Or in other words, how would I make it so the users location’s weather is displayed rather than Portland, OR?

    Thanks,

    Joe

  • ChrisPLamb

    I agree with everyone else, wonderful tutorial, thanks.

    We are now getting intermittent- MKReverseGeocoder ‘MKErrorDomain error 4′ on our app. According to others on the web it’s believed to be a Google issue? Do you have any comments or suggestions for a work around? I’d hate to have to try another API.

    Thanks again,
    CPL

  • Paul Sheldon

    So, this morning between church stuff, now the final piece, a call to an object or instance with a number, build and run. Where do I put the call? And all that code after the command build and run to show me where to put the final piece. Ok.

    Am I going crazy or the author is seeing whether there is a live one out there not merely copying and pasting sequentially, but getting engaged with apple’s library and contextual clicks to it. Maybe an intermediate programmer just thinks out of lock step sequence with boundless nerve?

    Maybe I do, but only after brainstorming on something else to pick up nerve.

    Brainstorm bore fruit. Thank you for the out of context final piece.

    I could however burn out reading such a writing device. I always fear a brick wall just before I finish a reading milestone in theoretical physics that’s very heavy. That fear is hard work for long readings.

    So when you said “now the final piece and make sure to hook up in IB right” I got the same bad taste as Apple at Stanford throwing paparazzi updates at me a bit too fast and on my responsibility. Somebody says succeed or flunk and that sounds not like a course but a weeding that a Penny at Syanford fought the Stanford Binnet ranking attitude as I visited long ago while working Lockheed.

    I shall get back to article. When I’m timid mere misses of dots and periods and commas made me doubt I understood a great physicist who said he had a bad editor, but he freaked when I was a better one.

    Maybe, tough, I should pray for my own hail mary’s getting past my fears. O saw the dot missing, feel grateful to the author for the device of showing
    Me I was there seeing.

  • http://twitter.com/chaseacton Chase

    This is great. Now how do we get a 5-day forecast information for days of the week?

    Thanks,
    Chase
    chaseacton@gmail.com

  • Icelance11

    When I compiled i got the error

    ‘isinf’ was not declared in this scope

    in the MapKit.framework -> MKGeomtry.h -> MKMapRectIsNull(MKMapRect rect)

  • Ihmunro

     Hi Matt

    How would I go about updating it for Canadian Cities ?

    Iain

  • Ihmunro

     Hi Matt

    I think I have it working now for Calgary as an example and have managed to get the main temp in C, but am stuck as to how to get from F to C for the Today’s High and Low.

    Iain

  • https://me.yahoo.com/a/xEZSXuASx_Nuv5.bIisxlFPHyTaxC5rfyw--#0c4a4 Foren

    I’m having the same problem as Icelance11:

    isinf’ was not declared in this scope

    in the MapKit.framework -> MKGeomtry.h -> MKMapRectIsNull(MKMapRect rect)

  • John

    This tutorial was very helpful.   Worked nicely but only for US ZIP
    codes where the google URL conforms to RFC requirements.   A UK post
    code (e.g.  “BS7 9EX,UK”) does not obey the RFC protocol because of the
    space character.   I fixed this with a small change in
    ICB_WeatherConditions initWithQuery so that this tutorial will also be
    useful for post codes which include a space:

        NSURL *fixedURL;

        NSString *string;

       string = [NSString stringWithFormat:@  "http://www.google.com/ig/api?weather=%@",query];

       fixedURL = [NSURL URLWithString:[string   stringByAddingPercentEscapesUsingEncoding:NSUTF8StrinEncoding]];

     

    then replace next line as    initWithContentsOfURL:fixedURL

     

    John

  • Marc Didillon

    Thanks for the tutorial!

    This solution is working great for US Zip codes, but not for countries that uses characters, like “ä” oder “ü”, such as Germany. So better do following:

    CXMLDocument *parser = [[[CXMLDocument alloc] initWithContentsOfURL:[NSURL URLWithString:weatherURLString] encoding:NSISOLatin1StringEncoding options:0 error:nil] autorelease];This will work for all countries.You need to send ZIP-Country to get the weather for any other country.

  • Jsparks

    Greetings!  Your code has worked perfectly, excellent tutorial! I’m trying to follow your suggestion and use an XML feed other than the Google one you used in the example.  I’d like to use the WeatherBug API.

    I’m looking at the XML returned by the WeatherBug API and each item’s data is not stored in a “data=” container or tag or whatever.  It’s more like 72 – any suggestions on how to make your example work with this sort of XML feed?  I’ve tried leaving attributeForName: blank and that doesn’t work.  Just not sure what to do.

  • Sabanim

    Awsome! Thanks.  

    Does this refresh or do I have to tell it to?

  • http://www.facebook.com/SanctumLLC Brian Ash

    I am having trouble actually bringing in corelocation info to replace the static longitude and latitude that is hardcoded in the example above. Ive tried for about a day toying with it, but to no avail. I brought in locationgetter.h and .m and tried adding 

    - (void)locationManager:(CLLocation *)location {
         // This will get us something we can query Google’s Weather API with
        MKReverseGeocoder *geocoder = [[MKReverseGeocoder alloc] initWithCoordinate:location.coordinate];
        geocoder.delegate = self;
        [geocoder start];
    }to the view controller instead of the given static location. Am I over complicating this or just too dumb to follow :P  

blog comments powered by Disqus
canakkale canakkale canakkale balik tutma search canakkale vergi mevzuati