Skip to content

Commit

Permalink
Integration of live data
Browse files Browse the repository at this point in the history
  • Loading branch information
futureshape committed Aug 9, 2010
1 parent 9d00a7a commit d296442
Show file tree
Hide file tree
Showing 13 changed files with 607 additions and 80 deletions.
1 change: 0 additions & 1 deletion Classes/CycleHireAppDelegate.m
Expand Up @@ -72,7 +72,6 @@ - (void)application:(UIApplication *)application didReceiveLocalNotification:(UI
[simulateLocalNotification show];
[simulateLocalNotification release];
}

}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
Expand Down
21 changes: 12 additions & 9 deletions Classes/CycleHireLocation.h
Expand Up @@ -12,30 +12,33 @@
@interface CycleHireLocation : NSObject {
NSString *locationId;
NSString *locationName;
NSString *postcodeArea;
NSString *villageName;
CLLocationCoordinate2D coordinate;
NSUInteger capacity;
BOOL favourite;

// Dummy variables below at the moment:
NSUInteger bikesAvailable;
NSUInteger spacesAvailable;
}

@property(nonatomic, retain) NSString *locationId;
@property(nonatomic, retain) NSString *locationName;
@property(nonatomic, retain) NSString *postcodeArea;
@property(nonatomic, retain) NSString *villageName;
@property(nonatomic) CLLocationCoordinate2D coordinate;
@property(nonatomic) NSUInteger capacity;
@property(nonatomic) NSUInteger bikesAvailable;
@property(nonatomic) NSUInteger spacesAvailable;
@property(nonatomic,readonly) NSUInteger capacity;
@property(nonatomic) BOOL favourite;

- (id) initWithLocationId: (NSString *)_locationId
name:(NSString *)_locationName
postcode:(NSString *)_postcodeArea
location:(CLLocationCoordinate2D)_coordinate
capacity: (NSUInteger) _capacity;
name: (NSString *)_locationName
village: (NSString *)_villageName
location: (CLLocationCoordinate2D)_coordinate
bikesAvailable: (NSUInteger) _bikes
spacesAvailable: (NSUInteger) _spaces;
- (id) initWithAttributesArray:(NSArray *)array;

- (NSString *)localizedBikesAvailableText;
- (NSString *)localizedSpacesAvailableText;
- (NSString *)localizedCapacityText;
- (NSUInteger) capacity;
@end
54 changes: 35 additions & 19 deletions Classes/CycleHireLocation.m
Expand Up @@ -12,26 +12,26 @@ @implementation CycleHireLocation

@synthesize locationId;
@synthesize locationName;
@synthesize postcodeArea;
@synthesize villageName;
@synthesize coordinate;
@synthesize capacity;
@synthesize bikesAvailable;
@synthesize spacesAvailable;
@synthesize favourite;

- (id) initWithLocationId: (NSString *)_locationId
name:(NSString *)_locationName
postcode:(NSString *)_postcodeArea
location:(CLLocationCoordinate2D)_coordinate
capacity: (NSUInteger) _capacity {
name: (NSString *)_locationName
village: (NSString *)_villageName
location: (CLLocationCoordinate2D)_coordinate
bikesAvailable: (NSUInteger) _bikes
spacesAvailable: (NSUInteger) _spaces {

if (self = [super init]) {
self.locationId = _locationId;
self.locationName = _locationName;
self.postcodeArea = _postcodeArea;
self.villageName = _villageName;
self.coordinate = _coordinate;
self.capacity = _capacity;

spacesAvailable = self.capacity;
bikesAvailable = 0;
self.spacesAvailable = _spaces;
self.bikesAvailable = _bikes;
}

return self;
Expand All @@ -40,25 +40,27 @@ - (id) initWithLocationId: (NSString *)_locationId
- (id) initWithAttributesArray:(NSArray *)array {
NSString *_locationId = (NSString *)[array objectAtIndex:0];
NSString *_locationName = (NSString *)[array objectAtIndex:1];
NSString *_postcodeArea = (NSString *)[array objectAtIndex:2];
NSString *_villageName = (NSString *)[array objectAtIndex:2];

CLLocationCoordinate2D _coordinate;
_coordinate.latitude = [(NSString *)[array objectAtIndex:3] doubleValue];
_coordinate.longitude = [(NSString *)[array objectAtIndex:4] doubleValue];

NSUInteger _capacity = [(NSString *)[array objectAtIndex:5] integerValue];

NSUInteger _bikes = [(NSString *)[array objectAtIndex:5] integerValue];
NSUInteger _spaces = [(NSString *)[array objectAtIndex:6] integerValue];

return [self initWithLocationId:_locationId
name:_locationName
postcode:_postcodeArea
village:_villageName
location:_coordinate
capacity:_capacity];
bikesAvailable:_bikes
spacesAvailable:_spaces];
}

- (NSString *)description {
return [NSString stringWithFormat:@"CycleHireLocation: %@, %@ [%@] (%f,%f) c:%d",
self.locationName, self.postcodeArea, self.locationId,
self.coordinate.latitude, self.coordinate.longitude, self.capacity];
return [NSString stringWithFormat:@"CycleHireLocation: %@, %@ [%@] (%f,%f) b:%d s:%d c:%d",
self.locationName, self.villageName, self.locationId,
self.coordinate.latitude, self.coordinate.longitude, self.bikesAvailable, self.spacesAvailable, self.capacity];
}

- (NSString *)localizedBikesAvailableText {
Expand All @@ -81,5 +83,19 @@ - (NSString *)localizedSpacesAvailableText {
}
}

- (NSString *)localizedCapacityText {
if (self.capacity == 0) {
return NSLocalizedString(@"Out of order", nil);
} else if (self.capacity == 1) {
return NSLocalizedString(@"1 docking point", nil);
} else {
return [NSString stringWithFormat:NSLocalizedString(@"%d docking points", nil), self.capacity];
}
}


- (NSUInteger) capacity {
return self.spacesAvailable + self.bikesAvailable;
}

@end
17 changes: 17 additions & 0 deletions Classes/CycleHireLocations.h
Expand Up @@ -7,20 +7,37 @@
//

#import <Foundation/Foundation.h>
#import <Three20/Three20.h>
#import "CycleHireLocation.h"

#define FAVOURITES_FILE @"favourites.plist"
#define CYCLEHIRE_LOCATIONS_FILE @"cyclehire.csv"
#define LIVE_DATA_URL @"http://cyclehireapp.com/cyclehirelive/cyclehire.csv"
#define LIVE_DATA_UPDATED_NOTIFICATION @"LiveDataUpdated"
#define LIVE_DATA_TOO_OLD_NOTIFICATION @"LiveDataTooOld"
#define LIVE_DATA_MAX_AGE (10*60) // 10 minutes

@interface CycleHireLocations : NSObject {
NSMutableDictionary *locationsDictionary;
NSMutableArray *favouriteLocations;

NSString *favouritesPath;
NSString *csvDocPath;

TTURLRequest *updateRequest;

NSDate *lastUpdatedTimestamp;
}

@property(nonatomic, retain) NSDate *lastUpdatedTimestamp;

-(id) init;

-(NSArray *)allLocations;
-(NSArray *)favouriteLocations;
-(void)saveFavouriteLocations;
-(CycleHireLocation *)locationWithId: (NSString*) locationId;
- (void) startUpdateFromServer;
- (BOOL) freshDataAvailable;
- (NSDate *) timeStampDateFromString: (NSString *) timeStampString;
@end
107 changes: 100 additions & 7 deletions Classes/CycleHireLocations.m
Expand Up @@ -11,19 +11,39 @@

@implementation CycleHireLocations

@synthesize lastUpdatedTimestamp;

-(id) init {
if(self = [super init]) {

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];

// Load all locations

locationsDictionary = [[NSMutableDictionary alloc] init];

CSVParser *parser = [CSVParser new];
NSString *csvPath = [[NSBundle mainBundle] pathForResource:@"cyclehire" ofType:@"csv"];
[parser openFile: csvPath];
NSArray *hireLocations = [parser parseFile];

csvDocPath = [[documentsDirectory stringByAppendingPathComponent:CYCLEHIRE_LOCATIONS_FILE] retain];
NSString *csvBundlePath = [[NSBundle mainBundle] pathForResource:@"cyclehire" ofType:@"csv"];

if ([[NSFileManager defaultManager] fileExistsAtPath:csvDocPath]) {
[parser openFile: csvDocPath];
} else {
NSLog(@"CycleHireLocations: no list saved in documents, opening from bundle");
[parser openFile: csvBundlePath];
}

[parser setDelimiter:','];
NSMutableArray *hireLocations = [parser parseFile];
[parser closeFile];

NSString *timeStampString = [[hireLocations objectAtIndex:0] objectAtIndex:0];
NSDate *newTimeStamp = [self timeStampDateFromString:timeStampString];
self.lastUpdatedTimestamp = newTimeStamp;
NSLog(@"Loaded initial data with timestamp: %@", lastUpdatedTimestamp);
[hireLocations removeObjectAtIndex:0]; // Remove first line comment

for (NSArray *hireLocationRecord in hireLocations) {
CycleHireLocation *location = [[CycleHireLocation alloc] initWithAttributesArray:hireLocationRecord];
[locationsDictionary setValue:location forKey:location.locationId];
Expand All @@ -34,9 +54,6 @@ -(id) init {
NSLog(@"Loaded %d cycle hire locations", [locationsDictionary count]);

// Load favourites

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
favouritesPath = [[NSString stringWithFormat:@"%@/%@", documentsDirectory, FAVOURITES_FILE] retain];

NSArray *favouriteLocationIDs = [NSMutableArray arrayWithContentsOfFile:favouritesPath];
Expand Down Expand Up @@ -89,11 +106,87 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
}
}

- (void) startUpdateFromServer {
if(updateRequest != nil) {
[updateRequest cancel];
[updateRequest release];
}

updateRequest = [[TTURLRequest alloc] initWithURL:LIVE_DATA_URL delegate:self];
updateRequest.cachePolicy = TTURLRequestCachePolicyNone;
updateRequest.response = [[[TTURLDataResponse alloc] init] autorelease];
updateRequest.httpMethod = @"GET";
[updateRequest send];
}

- (void)requestDidFinishLoad:(TTURLRequest*)_request {
TTURLDataResponse *response = _request.response;

NSString *responseString = [[NSString alloc] initWithData:response.data encoding:NSUTF8StringEncoding];
[responseString writeToFile:csvDocPath atomically:YES encoding:NSUTF8StringEncoding error:NULL];

CSVParser *parser = [CSVParser new];
[parser openFile:csvDocPath];
[parser setDelimiter:','];
NSMutableArray *hireLocations = [parser parseFile];
[parser closeFile];

NSString *timeStampString = [[hireLocations objectAtIndex:0] objectAtIndex:0];
NSDate *newTimeStamp = [self timeStampDateFromString:timeStampString];

if (newTimeStamp == nil) return;

self.lastUpdatedTimestamp = newTimeStamp;
NSLog(@"Downloaded live data with timestamp: %@", lastUpdatedTimestamp);

[hireLocations removeObjectAtIndex:0]; // Remove timestamp from data (1st line)

for (NSArray *hireLocationRecord in hireLocations) {
CycleHireLocation *locationToUpdate = [self locationWithId:[hireLocationRecord objectAtIndex:0]];

NSUInteger newBikes = [(NSString *)[hireLocationRecord objectAtIndex:5] integerValue];
NSUInteger newSpaces = [(NSString *)[hireLocationRecord objectAtIndex:6] integerValue];

// TEST: by assigning random values below
locationToUpdate.bikesAvailable = newBikes;
locationToUpdate.spacesAvailable = newSpaces;
// locationToUpdate.bikesAvailable = rand() % 10;
// locationToUpdate.spacesAvailable = rand() % 10;
}

[parser release];

[[NSNotificationCenter defaultCenter] postNotificationName:LIVE_DATA_UPDATED_NOTIFICATION object:self];
}

- (void)request:(TTURLRequest*)_request didFailLoadWithError:(NSError*)error {
if (![self freshDataAvailable]) {
[[NSNotificationCenter defaultCenter] postNotificationName:LIVE_DATA_TOO_OLD_NOTIFICATION object:self];
}
}

- (BOOL) freshDataAvailable {
NSTimeInterval dataAge = -[self.lastUpdatedTimestamp timeIntervalSinceNow];

return !(dataAge > LIVE_DATA_MAX_AGE);
}

- (NSDate *) timeStampDateFromString: (NSString *) timeStampString {
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateFormat:@"yyyy-MM-dd hh:mm:ss"];
[df setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
[df setLenient:YES];
NSDate *timeStamp = [df dateFromString: timeStampString];
[df release];
return timeStamp;
}

- (void) dealloc {
[super dealloc];
[favouriteLocations release];
[locationsDictionary release];
[favouritesPath release];
[csvDocPath release];
}

@end
4 changes: 3 additions & 1 deletion Classes/FavouritesListDataSource.h
Expand Up @@ -12,10 +12,12 @@
#import "CycleHireLocations.h";

@interface FavouritesListDataSource : TTListDataSource {
CycleHireLocations *cycleHireLocations;
NSArray *favouriteLocations;
}

- (id)initWithFavouriteLocations: (NSArray *) favouriteLocations;
- (id)initWithCycleHireLocations: (CycleHireLocations *) cycleHireLocations;
-(TTTableItem*) tableItemForLocation:(CycleHireLocation*)location;
-(void) refreshData;

@end
34 changes: 22 additions & 12 deletions Classes/FavouritesListDataSource.m
Expand Up @@ -10,12 +10,11 @@

@implementation FavouritesListDataSource

- (id)initWithFavouriteLocations: (NSArray *) _favouriteLocations {
favouriteLocations = _favouriteLocations;
- (id)initWithCycleHireLocations: (CycleHireLocations *) _cycleHireLocations {
cycleHireLocations = _cycleHireLocations;
favouriteLocations = [cycleHireLocations favouriteLocations];
if (self = [super init]) {
for (CycleHireLocation *location in favouriteLocations) {
[self.items addObject:[self tableItemForLocation:location]];
}
[self refreshData];
}
return self;
}
Expand All @@ -31,15 +30,26 @@ - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEd
}
}

-(TTTableItem*) tableItemForLocation:(CycleHireLocation*)location {
NSString *title = [NSString stringWithFormat:@"%@, %@", location.locationName, location.postcodeArea];
// NSString *subtitle = [NSString stringWithFormat:@"%@, %@",
// [location localizedBikesAvailableText],
// [location localizedSpacesAvailableText]];
-(void) refreshData {
[self.items removeAllObjects];
for (CycleHireLocation *location in favouriteLocations) {
[self.items addObject:[self tableItemForLocation:location]];
}
}

NSString *subtitle = [NSString stringWithFormat:@"%d docking points", location.capacity];
-(TTTableItem*) tableItemForLocation:(CycleHireLocation*)location {
NSString *title = [NSString stringWithFormat:@"%@, %@", location.locationName, location.villageName];

NSString *subtitle;
if ([cycleHireLocations freshDataAvailable]) {
subtitle = [NSString stringWithFormat:@"%@, %@",
[location localizedBikesAvailableText],
[location localizedSpacesAvailableText]];
} else {
subtitle = [location localizedCapacityText];
}

// Need to replaces slashes in TfL reference with the url-encoded alternative
// Need to replace slashes in TfL reference with the url-encoded alternative
NSString *encodedLocationId = [location.locationId stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
NSString *URL = [NSString stringWithFormat:@"cyclehire://map/cycleHireLocation/%@", encodedLocationId];
return [TTTableSubtitleItem itemWithText:title subtitle:subtitle URL:URL];
Expand Down

0 comments on commit d296442

Please sign in to comment.