Authored by Justin R. Miller

enhancements for interactivity & composite sources

* added RMCompositeSource (Core Graphics compositing)
 * added map view interactivity querying (not just sources)
 * interactivity parsing cleanups
 * TMS/XYZ scheme cleanups
//
// RMCompositeSource.h
// MapView
//
// Created by Justin Miller on 4/19/12.
// Copyright (c) 2012 MapBox / Development Seed. All rights reserved.
//
#import "RMTileSource.h"
@interface RMCompositeSource : NSObject <RMTileSource>
{
RMFractalTileProjection *tileProjection;
}
@property (nonatomic, retain) NSMutableArray *compositeSources;
- (id)initWithTileSource:(id <RMTileSource>)initialTileSource;
- (UIImage *)imageForTile:(RMTile)tile inCache:(RMTileCache *)tileCache;
- (void)cancelAllDownloads;
- (RMFractalTileProjection *)mercatorToTileProjection;
- (RMProjection *)projection;
- (float)minZoom;
- (void)setMinZoom:(NSUInteger)aMinZoom;
- (float)maxZoom;
- (void)setMaxZoom:(NSUInteger)aMaxZoom;
- (int)tileSideLength;
- (void)setTileSideLength:(NSUInteger)aTileSideLength;
- (RMSphericalTrapezium)latitudeLongitudeBoundingBox;
- (NSString *)uniqueTilecacheKey;
- (NSString *)shortName;
- (NSString *)longDescription;
- (NSString *)shortAttribution;
- (NSString *)longAttribution;
- (void)didReceiveMemoryWarning;
@end
... ...
//
// RMCompositeSource.m
// MapView
//
// Created by Justin Miller on 4/19/12.
// Copyright (c) 2012 MapBox / Development Seed. All rights reserved.
//
#import "RMCompositeSource.h"
#import "RMAbstractMercatorTileSource.h"
@implementation RMCompositeSource
@synthesize compositeSources;
- (id)initWithTileSource:(id <RMTileSource>)initialTileSource
{
self = [super init];
if (self)
compositeSources = [[NSMutableArray arrayWithObject:initialTileSource] retain];
return self;
}
- (void)dealloc
{
[compositeSources release];
[super dealloc];
}
- (UIImage *)imageForTile:(RMTile)tile inCache:(RMTileCache *)tileCache
{
// FIXME: cache
UIImage *image = nil;
for (id <RMTileSource>tileSource in self.compositeSources)
{
UIImage *sourceImage = [tileSource imageForTile:tile inCache:tileCache];
if (sourceImage)
{
if (image != nil)
{
UIGraphicsBeginImageContext(image.size);
[image drawAtPoint:CGPointMake(0,0)];
[sourceImage drawAtPoint:CGPointMake(0,0)];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
else
{
image = sourceImage;
}
}
}
return image;
}
- (void)cancelAllDownloads
{
NSLog(@"cancelAllDownloads");
}
- (RMFractalTileProjection *)mercatorToTileProjection
{
if ( ! tileProjection)
{
tileProjection = [[RMFractalTileProjection alloc] initFromProjection:[self projection]
tileSideLength:[self tileSideLength]
maxZoom:[self maxZoom]
minZoom:[self minZoom]];
}
return tileProjection;
}
- (RMProjection *)projection
{
return [RMProjection googleProjection];
}
- (float)minZoom
{
return kDefaultMinTileZoom;
}
- (void)setMinZoom:(NSUInteger)aMinZoom
{
NSLog(@"setMinZoom:");
}
- (float)maxZoom
{
return kDefaultMaxTileZoom;
}
- (void)setMaxZoom:(NSUInteger)aMaxZoom
{
NSLog(@"setMaxZoom:");
}
- (int)tileSideLength
{
return kDefaultTileSize;
}
- (void)setTileSideLength:(NSUInteger)aTileSideLength
{
NSLog(@"setTileSideLength:");
}
- (RMSphericalTrapezium)latitudeLongitudeBoundingBox
{
return kDefaultLatLonBoundingBox;
}
- (NSString *)uniqueTilecacheKey
{
return NSStringFromClass([self class]);
}
- (NSString *)shortName
{
return @"shortName";
}
- (NSString *)longDescription
{
return @"longDescription";
}
- (NSString *)shortAttribution
{
return @"shortAttribution";
}
- (NSString *)longAttribution
{
return @"longAttribution";
}
- (void)didReceiveMemoryWarning
{
NSLog(@"didReceiveMemoryWarning");
}
@end
... ...
... ... @@ -33,11 +33,10 @@
// Based on the UTFGrid specification: https://github.com/mapbox/utfgrid-spec
//
#import "RMMapView.h"
#import "RMMBTilesSource.h"
#import "RMMapBoxSource.h"
@class RMMapView;
// Interactivity currently supports two types of output: 'teaser'
// and 'full'. Ideal for master/detail interfaces or for showing
// a MapKit-style detail-toggling point callout.
... ... @@ -47,6 +46,31 @@ typedef enum {
RMInteractiveSourceOutputTypeFull = 1,
} RMInteractiveSourceOutputType;
@protocol RMInteractiveMapView
@required
// Query if a map view supports interactivity features.
//
- (BOOL)supportsInteractivity;
// Get the HTML-formatted output for a given point on a given map view.
//
- (NSString *)formattedOutputOfType:(RMInteractiveSourceOutputType)outputType forPoint:(CGPoint)point;
@end
#pragma mark -
@interface RMMapView (RMInteractiveSource) <RMInteractiveMapView>
- (BOOL)supportsInteractivity;
- (NSString *)formattedOutputOfType:(RMInteractiveSourceOutputType)outputType forPoint:(CGPoint)point;
@end
#pragma mark -
@protocol RMInteractiveSource
@required
... ...
... ... @@ -33,7 +33,7 @@
#import "RMInteractiveSource.h"
#import "RMMapView.h"
#import "RMCompositeSource.h"
#import "FMDatabase.h"
#import "FMDatabaseQueue.h"
... ... @@ -185,7 +185,7 @@ RMTilePoint RMInteractiveSourceNormalizedTilePointForMapView(CGPoint point, RMMa
{
NSString *formattedOutput = nil;
id <RMTileSource>source = mapView.tileSource;
id <RMTileSource, RMInteractiveSource>source = [mapView interactiveTileSource];
NSDictionary *interactivityDictionary = [(id <RMInteractiveSourcePrivate>)source interactivityDictionaryForPoint:point inMapView:mapView];
... ... @@ -291,6 +291,78 @@ RMTilePoint RMInteractiveSourceNormalizedTilePointForMapView(CGPoint point, RMMa
@end
#pragma mark -
#pragma mark RMMapView Interactivity
@interface RMMapView (RMInteractiveSourcePrivate) <RMInteractiveSourcePrivate>
- (id <RMTileSource, RMInteractiveSource>)interactiveTileSource;
- (NSDictionary *)interactivityDictionaryForPoint:(CGPoint)point;
- (NSString *)interactivityFormatterTemplate;
@end
@implementation RMMapView (RMInteractiveSource)
- (id <RMTileSource, RMInteractiveSource>)interactiveTileSource
{
id <RMTileSource, RMInteractiveSource>interactiveTileSource = nil;
if ([self.tileSource isKindOfClass:[RMCompositeSource class]])
{
// currently, we iterate top-down and return the first interactive source
//
for (id <RMTileSource>source in [[((RMCompositeSource *)self.tileSource).compositeSources reverseObjectEnumerator] allObjects])
{
NSLog(@"checking %@", source);
if (([source isKindOfClass:[RMMBTilesSource class]] || [source isKindOfClass:[RMMapBoxSource class]]) &&
[source conformsToProtocol:@protocol(RMInteractiveSource)] &&
[(id <RMInteractiveSource>)source supportsInteractivity])
{
interactiveTileSource = (id <RMTileSource, RMInteractiveSource>)source;
break;
}
}
}
else
{
if (([self.tileSource isKindOfClass:[RMMBTilesSource class]] || [self.tileSource isKindOfClass:[RMMapBoxSource class]]) &&
[self.tileSource conformsToProtocol:@protocol(RMInteractiveSource)] &&
[(id <RMInteractiveSource>)self.tileSource supportsInteractivity])
{
interactiveTileSource = (id <RMTileSource, RMInteractiveSource>)self.tileSource;
}
}
NSLog(@"determined interactive source as %@", interactiveTileSource);
return interactiveTileSource;
}
- (BOOL)supportsInteractivity
{
return ([self interactiveTileSource] != nil);
}
- (NSDictionary *)interactivityDictionaryForPoint:(CGPoint)point
{
return [(id <RMInteractiveSourcePrivate>)[self interactiveTileSource] interactivityDictionaryForPoint:point inMapView:self];
}
- (NSString *)interactivityFormatterTemplate
{
return [(id <RMInteractiveSourcePrivate>)[self interactiveTileSource] interactivityFormatterTemplate];
}
- (NSString *)formattedOutputOfType:(RMInteractiveSourceOutputType)outputType forPoint:(CGPoint)point
{
return [(id <RMInteractiveSourcePrivate>)[self interactiveTileSource] formattedOutputOfType:outputType forPoint:point inMapView:self];
}
@end
#pragma mark -
#pragma mark MBTiles Interactivity
@interface RMMBTilesSource (RMInteractiveSourcePrivate) <RMInteractiveSourcePrivate>
... ... @@ -414,7 +486,7 @@ RMTilePoint RMInteractiveSourceNormalizedTilePointForMapView(CGPoint point, RMMa
[results close];
}];
return template;
return ([template length] ? template : nil);
}
- (NSString *)formattedOutputOfType:(RMInteractiveSourceOutputType)outputType forPoint:(CGPoint)point inMapView:(RMMapView *)mapView
... ... @@ -459,7 +531,14 @@ RMTilePoint RMInteractiveSourceNormalizedTilePointForMapView(CGPoint point, RMMa
- (NSDictionary *)interactivityDictionaryForPoint:(CGPoint)point inMapView:(RMMapView *)mapView;
{
if ([self.infoDictionary objectForKey:@"grids"])
NSString *gridURLString = nil;
if ([self.infoDictionary objectForKey:@"grids"] && [[self.infoDictionary objectForKey:@"grids"] isKindOfClass:[NSArray class]])
gridURLString = [[self.infoDictionary objectForKey:@"grids"] objectAtIndex:0];
else
gridURLString = [self.infoDictionary objectForKey:@"gridURL"];
if ([gridURLString length])
{
RMTilePoint tilePoint = RMInteractiveSourceNormalizedTilePointForMapView(point, mapView);
... ... @@ -467,8 +546,6 @@ RMTilePoint RMInteractiveSourceNormalizedTilePointForMapView(CGPoint point, RMMa
NSInteger x = tilePoint.tile.x;
NSInteger y = tilePoint.tile.y;
NSString *gridURLString = [[self.infoDictionary objectForKey:@"grids"] objectAtIndex:0];
gridURLString = [gridURLString stringByReplacingOccurrencesOfString:@"{z}" withString:[[NSNumber numberWithInteger:zoom] stringValue]];
gridURLString = [gridURLString stringByReplacingOccurrencesOfString:@"{x}" withString:[[NSNumber numberWithInteger:x] stringValue]];
gridURLString = [gridURLString stringByReplacingOccurrencesOfString:@"{y}" withString:[[NSNumber numberWithInteger:y] stringValue]];
... ... @@ -525,7 +602,7 @@ RMTilePoint RMInteractiveSourceNormalizedTilePointForMapView(CGPoint point, RMMa
- (NSString *)interactivityFormatterTemplate
{
if ([self.infoDictionary objectForKey:@"template"])
if ([self.infoDictionary objectForKey:@"template"] && [[self.infoDictionary objectForKey:@"template"] length])
return [self.infoDictionary objectForKey:@"template"];
return nil;
... ...
... ... @@ -49,8 +49,6 @@
{
if (self = [super init])
{
NSAssert([NSJSONSerialization class], @"JSON serialization not supported by SDK");
infoDictionary = (NSDictionary *)[[NSJSONSerialization JSONObjectWithData:[tileJSON dataUsingEncoding:NSUTF8StringEncoding]
options:0
error:nil] retain];
... ... @@ -84,8 +82,18 @@
if ([[referenceURL pathExtension] isEqualToString:@"json"] && (dataObject = [NSString stringWithContentsOfURL:referenceURL encoding:NSUTF8StringEncoding error:nil]) && dataObject)
return [self initWithTileJSON:dataObject];
else if ([[referenceURL pathExtension] isEqualToString:@"plist"] && (dataObject = [[[NSDictionary alloc] initWithContentsOfURL:referenceURL] autorelease]) && dataObject)
return [self initWithInfo:dataObject];
else if ([[referenceURL pathExtension] isEqualToString:@"plist"])
{
NSMutableDictionary *mutableInfoDictionary = [NSMutableDictionary dictionaryWithContentsOfURL:referenceURL];
if (mutableInfoDictionary)
{
if ( ! [mutableInfoDictionary objectForKey:@"scheme"])
[mutableInfoDictionary setObject:@"tms" forKey:@"scheme"]; // assume older plists are TMS, not XYZ per TileJSON default
return [self initWithInfo:mutableInfoDictionary];
}
}
return nil;
}
... ...
... ... @@ -136,6 +136,8 @@
B8F3FC650EA2E792004D8F85 /* RMMarker.m in Sources */ = {isa = PBXBuildFile; fileRef = B8F3FC630EA2E792004D8F85 /* RMMarker.m */; };
D1437B36122869E400888DAE /* RMDBMapSource.m in Sources */ = {isa = PBXBuildFile; fileRef = D1437B32122869E400888DAE /* RMDBMapSource.m */; };
D1437B37122869E400888DAE /* RMDBMapSource.h in Headers */ = {isa = PBXBuildFile; fileRef = D1437B33122869E400888DAE /* RMDBMapSource.h */; };
DD103E241540E3CF00AA65DD /* RMCompositeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = DD103E221540E3CF00AA65DD /* RMCompositeSource.h */; };
DD103E251540E3CF00AA65DD /* RMCompositeSource.m in Sources */ = {isa = PBXBuildFile; fileRef = DD103E231540E3CF00AA65DD /* RMCompositeSource.m */; };
DD2B374514CF8041008DE8CB /* FMDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = DD2B373F14CF8041008DE8CB /* FMDatabase.h */; };
DD2B374614CF8041008DE8CB /* FMDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = DD2B374014CF8041008DE8CB /* FMDatabase.m */; };
DD2B374714CF8041008DE8CB /* FMDatabaseAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = DD2B374114CF8041008DE8CB /* FMDatabaseAdditions.h */; };
... ... @@ -301,6 +303,8 @@
B8F3FC630EA2E792004D8F85 /* RMMarker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMMarker.m; sourceTree = "<group>"; };
D1437B32122869E400888DAE /* RMDBMapSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMDBMapSource.m; sourceTree = "<group>"; };
D1437B33122869E400888DAE /* RMDBMapSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMDBMapSource.h; sourceTree = "<group>"; };
DD103E221540E3CF00AA65DD /* RMCompositeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMCompositeSource.h; sourceTree = "<group>"; };
DD103E231540E3CF00AA65DD /* RMCompositeSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMCompositeSource.m; sourceTree = "<group>"; };
DD2B373F14CF8041008DE8CB /* FMDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabase.h; sourceTree = "<group>"; };
DD2B374014CF8041008DE8CB /* FMDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabase.m; sourceTree = "<group>"; };
DD2B374114CF8041008DE8CB /* FMDatabaseAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabaseAdditions.h; sourceTree = "<group>"; };
... ... @@ -397,6 +401,8 @@
DD2B375414CF8197008DE8CB /* RMMBTilesSource.m */,
DD6380DB152E72880074E66E /* RMMapBoxSource.h */,
DD6380DC152E72880074E66E /* RMMapBoxSource.m */,
DD103E221540E3CF00AA65DD /* RMCompositeSource.h */,
DD103E231540E3CF00AA65DD /* RMCompositeSource.m */,
);
name = "Map sources";
sourceTree = "<group>";
... ... @@ -749,6 +755,7 @@
DDC4BEEB152E3DE700089409 /* GRMustacheTemplateRepository.h in Headers */,
DDC4BEEC152E3DE700089409 /* GRMustacheVersion.h in Headers */,
DD6380DD152E72880074E66E /* RMMapBoxSource.h in Headers */,
DD103E241540E3CF00AA65DD /* RMCompositeSource.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
... ... @@ -967,6 +974,7 @@
16FFF2CC14E3DBF700A170EC /* RMMapQuestOpenAerialSource.m in Sources */,
DD6380DE152E72880074E66E /* RMMapBoxSource.m in Sources */,
DDC4BEF2152E3FAE00089409 /* RMInteractiveSource.m in Sources */,
DD103E251540E3CF00AA65DD /* RMCompositeSource.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
... ...