Wednesday, 28 August 2013

Weather Map


  • Documentation
========================================================================
ABSTRACT
Demonstrates the use of the MapKit framework, displaying a map view with custom MKAnnotationViews.  An annotation object on a map is any object that conforms to the MKAnnotation protocol and is displayed on the screen as a MKAnnotationView.  Through the use of the MKAnnotation protocol and MKAnnotationView, this application identifies four major cities in North America with fictitious weather information.

========================================================================
DISCUSSION

These cities and their weather data are stored on disk using Core Data.  Maps can potentially have dozens or hundreds of annotations, which is why the use of Core Data is demonstrated here.  Core Data is a rich and sophisticated object graph management framework capable of dealing with large volumes of data.

Important:
The MapKit framework uses Google services to provide map data. Use of this class and the associated interfaces binds you to the Google Maps/Google Earth API terms of service. You can find these terms of service mentioned in the header section of "MKMapView.h".

========================================================================
BUILD REQUIREMENTS

iOS 4.0 SDK

========================================================================
RUNTIME REQUIREMENTS

iPhone OS 3.2 or later

========================================================================
PACKAGING LIST

AppDelegate
Configures and displays the application window and navigation controller.

MapViewController
Controls the MKMapView and leverages the WeatherServer class to display each weather location.

WeatherAnnotationView
The UIView or MKAnnotationView for drawing each weather location's data.

WeatherItem
The weather object representing each location on the map.

WeatherServer
Manages and serves all weather locations backed by Core Data.

========================================================================

Copyright (C) 2010 Apple Inc. All rights reserved.


  • Design  

File Structure



  • Code
   AppDelegate.h

@class WeatherServer;
@class MapViewController;

@interface AppDelegate : NSObject <UIApplicationDelegate>
{
    WeatherServer *weatherServer;
    MapViewController *mapViewController;
    UIWindow *window;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet WeatherServer *weatherServer;
@property (nonatomic, retain) IBOutlet MapViewController *mapViewController;

@end


-----------------------------------------------------------

AppDelegate.m


#import "AppDelegate.h"
#import "MapViewController.h"
#import "WeatherServer.h"
#import "WeatherItem.h"

@implementation AppDelegate

@synthesize window, weatherServer, mapViewController;

- (void)applicationDidFinishLaunching:(UIApplication *)application
{
    [window addSubview:mapViewController.view];
[window makeKeyAndVisible];
    mapViewController.weatherServer = weatherServer;
    [mapViewController mapView:mapViewController.mapView regionDidChangeAnimated:NO];
}

- (void)dealloc
{
[window release];
[super dealloc];
}

@end

-----------------------------------------------------------

MapViewController.h

#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>

@class WeatherServer;

@interface MapViewController : UIViewController <MKMapViewDelegate>
{
    UINavigationBar *titleBar;
    WeatherServer *weatherServer;
    MKMapView *mapView;
}

@property (nonatomic, retain) IBOutlet UINavigationBar *titleBar;
@property (nonatomic, retain) IBOutlet MKMapView *mapView;
@property (nonatomic, retain) IBOutlet WeatherServer *weatherServer;

@end

-----------------------------------------------------------
MapViewController.m


#import "MapViewController.h"
#import "WeatherServer.h"
#import "WeatherItem.h"
#import "WeatherAnnotationView.h"


@implementation MapViewController

@synthesize titleBar, mapView, weatherServer;

- (void)viewDidLoad
{
    // go to North America
    MKCoordinateRegion newRegion;
    newRegion.center.latitude = 37.37;
    newRegion.center.longitude = -96.24;
    newRegion.span.latitudeDelta = 28.49;
    newRegion.span.longitudeDelta = 31.025;

    [self.mapView setRegion:newRegion animated:NO];
}

- (void)viewDidUnload
{
self.titleBar = nil;
self.mapView = nil;
}

- (void)dealloc
{
    [titleBar release];
    [mapView release];
    [super dealloc];
}


#pragma mark Map View Delegate methods

- (void)mapView:(MKMapView *)map regionDidChangeAnimated:(BOOL)animated
{
    NSArray *oldAnnotations = mapView.annotations;
    [mapView removeAnnotations:oldAnnotations];
 
    NSArray *weatherItems = [weatherServer weatherItemsForMapRegion:mapView.region maximumCount:4];
    [mapView addAnnotations:weatherItems];
}

- (MKAnnotationView *)mapView:(MKMapView *)map viewForAnnotation:(id <MKAnnotation>)annotation
{
    static NSString *AnnotationViewID = @"annotationViewID";
        
    WeatherAnnotationView *annotationView =
            (WeatherAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationViewID];
    if (annotationView == nil)
    {
        annotationView = [[[WeatherAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationViewID] autorelease];
    }
    
    annotationView.annotation = annotation;
    
    return annotationView;
}

@end

-----------------------------------------------------------
WeatherAnnotationView.h


#import <MapKit/MapKit.h>

@interface WeatherAnnotationView : MKAnnotationView
{ }

@end

-----------------------------------------------------------

WeatherAnnotationView.m

#import "WeatherAnnotationView.h"
#import "WeatherItem.h"


@implementation WeatherAnnotationView

- (id)initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
    if (self != nil)
    {
        CGRect frame = self.frame;
        frame.size = CGSizeMake(60.0, 85.0);
        self.frame = frame;
        self.backgroundColor = [UIColor clearColor];
        self.centerOffset = CGPointMake(30.0, 42.0);
    }
    return self;
}




- (void)setAnnotation:(id <MKAnnotation>)annotation
{
    [super setAnnotation:annotation];
    
    // this annotation view has custom drawing code.  So when we reuse an annotation view

    // (through MapView's delegate "dequeueReusableAnnoationViewWithIdentifier" which returns non-nil)

    // we need to have it redraw the new annotation data.
    //
    // for any other custom annotation view which has just contains a simple image, this won't be needed
    //

    [self setNeedsDisplay];
}



- (void)drawRect:(CGRect)rect
{
    WeatherItem *weatherItem = (WeatherItem *)self.annotation;

    if (weatherItem != nil)
    {
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextSetLineWidth(context, 1);
        
        // draw the gray pointed shape:

        CGMutablePathRef path = CGPathCreateMutable();
        CGPathMoveToPoint(path, NULL, 14.0, 0.0);
        CGPathAddLineToPoint(path, NULL, 0.0, 0.0); 
        CGPathAddLineToPoint(path, NULL, 55.0, 50.0); 
        CGContextAddPath(context, path);
        CGContextSetFillColorWithColor(context, [UIColor lightGrayColor].CGColor);

        CGContextSetStrokeColorWithColor(context, [UIColor grayColor].CGColor);

        CGContextDrawPath(context, kCGPathFillStroke);
        CGPathRelease(path);
        
        // draw the cyan rounded box

        path = CGPathCreateMutable();
        CGPathMoveToPoint(path, NULL, 15.0, 0.5);

        CGPathAddArcToPoint(path, NULL, 59.5, 00.5, 59.5, 5.0, 5.0);
        CGPathAddArcToPoint(path, NULL, 59.5, 69.5, 55.0, 69.5, 5.0);
        CGPathAddArcToPoint(path, NULL, 10.5, 69.5, 10.5, 64.0, 5.0);
        CGPathAddArcToPoint(path, NULL, 10.5, 00.5, 15.0, 0.5, 5.0);
        CGContextAddPath(context, path);

        CGContextSetFillColorWithColor(context, [UIColor cyanColor].CGColor);

        CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);

        CGContextDrawPath(context, kCGPathFillStroke);

        CGPathRelease(path);
        
        NSInteger high = [weatherItem.high integerValue];
        NSInteger low = [weatherItem.low integerValue];

        // draw the temperature string and weather graphic

        NSString *temperature = [NSString stringWithFormat:@"%@\n%d / %d", weatherItem.place, high, low];

        [[UIColor blackColor] set];

        [temperature drawInRect:CGRectMake(15.0, 5.0, 50.0, 40.0) withFont:[UIFont systemFontOfSize:11.0]];

        NSString *imageName = nil;

        switch ([weatherItem.condition integerValue])
        {
            case Sunny:
                imageName = @"sunny.png";
                break;
            case PartlyCloudy:
                imageName = @"partly_cloudy.png";
                break;
            case Cloudy:
                imageName = @"cloudy.png";             
                break;
            default:
                imageName = @"partly_cloudy.png";
                break;
        }

        [[UIImage imageNamed:imageName] drawInRect:CGRectMake(12.5, 28.0, 45.0, 45.0)];
    }
}

@end


-----------------------------------------------------------
WeatherServer.h


#import <CoreData/CoreData.h>
#import <MapKit/MapKit.h>

@interface WeatherServer : NSObject
{
    NSManagedObjectContext *managedObjectContext;    
    NSPersistentStoreCoordinator *persistentStoreCoordinator;
}

@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;

@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;

- (NSArray *)weatherItemsForMapRegion:(MKCoordinateRegion)region maximumCount:(NSInteger)maxCount;

@end

-----------------------------------------------------------
WeatherServer.m


#import "WeatherServer.h"
#import "WeatherItem.h"


@implementation WeatherServer

- (void)dealloc
{
    [managedObjectContext release];
    [persistentStoreCoordinator release];
[super dealloc];
}

// Returns the managed object context for the application.

// If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.

//
- (NSManagedObjectContext *)managedObjectContext
{
    if (managedObjectContext == nil)
    {
        managedObjectContext = [[NSManagedObjectContext alloc] init];
        [managedObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
        
        NSError *error = nil;
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        [fetchRequest setEntity:[NSEntityDescription entityForName:@"WeatherItem" inManagedObjectContext:self.managedObjectContext]];

        NSArray *fetchedItems = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];

        if (fetchedItems.count == 0)
        {
            // fill in the new WeatherMap.sqlite found in the application's Documents directory
            NSManagedObjectContext *moc = self.managedObjectContext;
            NSEntityDescription *ent = [NSEntityDescription entityForName:@"WeatherItem" inManagedObjectContext:moc];
            WeatherItem *item = nil;
            
            item = [[WeatherItem alloc] initWithEntity:ent insertIntoManagedObjectContext:moc];

            item.place = @"S.F.";

            item.latitude = [NSNumber numberWithDouble:37.779941];

            item.longitude = [NSNumber numberWithDouble:-122.417908];

            item.high = [NSNumber numberWithInteger:80];

            item.low = [NSNumber numberWithInteger:50];

            item.condition = [NSNumber numberWithInteger:Sunny];

            [item release]; // release the item - the context itself retains the necessary data

            
            item = [[WeatherItem alloc] initWithEntity:ent insertIntoManagedObjectContext:moc];

            item.place = @"Denver";

            item.latitude = [NSNumber numberWithDouble:39.752601];

            item.longitude = [NSNumber numberWithDouble:-104.982605];

            item.high = [NSNumber numberWithInteger:40];
            item.low = [NSNumber numberWithInteger:30];
            item.condition = [NSNumber numberWithInteger:Snow];
            [item release];
            
            item = [[WeatherItem alloc] initWithEntity:ent insertIntoManagedObjectContext:moc];

            item.place = @"Chicago";
            item.latitude = [NSNumber numberWithDouble:41.863425];

            item.longitude = [NSNumber numberWithDouble:-87.652359];

            item.high = [NSNumber numberWithInteger:45];
            item.low = [NSNumber numberWithInteger:29];
            item.condition = [NSNumber numberWithInteger:Snow];
            [item release];
            
            item = [[WeatherItem alloc] initWithEntity:ent insertIntoManagedObjectContext:moc];

            item.place = @"Seattle";

            item.latitude = [NSNumber numberWithDouble:47.615884];

            item.longitude = [NSNumber numberWithDouble:-122.332764];

            item.high = [NSNumber numberWithInteger:75];
            item.low = [NSNumber numberWithInteger:45];
            item.condition = [NSNumber numberWithInteger:Sunny];
            [item release];
            
            NSError *err;
            [moc save:&err];
        }
        [fetchRequest release];
    }
    
    return managedObjectContext;
}

// Returns the persistent store coordinator for the application.

// If the coordinator doesn't already exist, it is created and the application's store added to it.

//



- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (persistentStoreCoordinator == nil)
    {
        // apply a store URL to our persistentStoreCoordinator, which will create a new WeatherMap.sqlite file if necessary

        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

        NSString *weatherMapPath = [[paths lastObject] stringByAppendingPathComponent:@"WeatherMap.sqlite"];

        NSURL *storeUrl = [NSURL fileURLWithPath:weatherMapPath];

        NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];

        persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];

        
        NSError *error = nil;
        if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error])
        {
            /*
             Replace this implementation with code to handle the error appropriately.
             
             abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
             
             Typical reasons for an error here include:
             * The persistent store is not accessible
             * The schema for the persistent store is incompatible with current managed object model
             Check the error message to determine what the actual problem was.
             */
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        }
    }
    
    return persistentStoreCoordinator;
}

- (NSArray *)weatherItemsForMapRegion:(MKCoordinateRegion)region maximumCount:(NSInteger)maxCount
{
    NSMutableArray *weatherItems = [NSMutableArray array];
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    [fetchRequest setEntity:[NSEntityDescription entityForName:@"WeatherItem" inManagedObjectContext:self.managedObjectContext]];
    
    NSNumber *latitudeStart = [NSNumber numberWithDouble:region.center.latitude - region.span.latitudeDelta/2.0];

    NSNumber *latitudeStop = [NSNumber numberWithDouble:region.center.latitude + region.span.latitudeDelta/2.0];

    NSNumber *longitudeStart = [NSNumber numberWithDouble:region.center.longitude - region.span.longitudeDelta/2.0];

    NSNumber *longitudeStop = [NSNumber numberWithDouble:region.center.longitude + region.span.longitudeDelta/2.0];

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"latitude>%@ AND latitude<%@ AND longitude>%@ AND longitude<%@", latitudeStart, latitudeStop, longitudeStart, longitudeStop];

    [fetchRequest setPredicate:predicate];
    NSMutableArray *sortDescriptors = [NSMutableArray array];
    [sortDescriptors addObject:[[[NSSortDescriptor alloc] initWithKey:@"latitude" ascending:YES] autorelease]];

    [sortDescriptors addObject:[[[NSSortDescriptor alloc] initWithKey:@"longitude" ascending:YES] autorelease]];

    [fetchRequest setSortDescriptors:sortDescriptors];

    [fetchRequest setReturnsObjectsAsFaults:NO];

    [fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:@"latitude", @"longitude", @"place", nil]];

    NSError *error = nil;

    NSArray *fetchedItems = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];

    if (fetchedItems == nil)
    {
        // an error occurred
        NSLog(@"fetch request resulted in an error %@, %@", error, [error userInfo]);
    }
    else
    {
        NSInteger countOfFetchedItems = [fetchedItems count];
        if (countOfFetchedItems > maxCount) {
            float index = 0, stride = (float)(countOfFetchedItems - 1)/ (float)maxCount;
            NSInteger countOfItemsToReturn = 0;
            while (countOfItemsToReturn < maxCount)
            {
                [weatherItems addObject:[fetchedItems objectAtIndex:(NSInteger)index]];
                index += stride;
                countOfItemsToReturn++;
            }
        }
        else
        {
            [weatherItems addObjectsFromArray:fetchedItems];
        }
    }
    [fetchRequest release];
    return weatherItems;
}

@end


-----------------------------------------------------------
WeatherItem.h

#import <CoreData/CoreData.h>
#import <MapKit/MapKit.h>

enum WeatherConditions
{
    Sunny = 0,
    PartlyCloudy,
    Cloudy,
    Showers,
    Thunderstorms,
    Snow
};

@interface WeatherItem : NSManagedObject <MKAnnotation>
{
    NSString *place;
    NSNumber *low;
    NSNumber *high;
    NSNumber *condition;
    NSNumber *latitude;
    NSNumber *longitude;
    
    CLLocationCoordinate2D coordinate;
}

@property (nonatomic, retain) NSString *place;
@property (nonatomic, retain) NSNumber *low;
@property (nonatomic, retain) NSNumber *high;
@property (nonatomic, retain) NSNumber *condition;
@property (nonatomic, retain) NSNumber *latitude;
@property (nonatomic, retain) NSNumber *longitude;

@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;

@end

-----------------------------------------------------------

WeatherItem.m


#import "WeatherItem.h"

@implementation WeatherItem 

@dynamic place, low, high, condition, longitude, latitude;

- (CLLocationCoordinate2D)coordinate
{
    coordinate.latitude = [self.latitude doubleValue];
    coordinate.longitude = [self.longitude doubleValue];
    return coordinate;
}

@end



No comments:

Post a Comment