Authored by Thomas Rasch

Merge branch 'feature/tiledlayer' into develop

Too many changes to show.

To preserve performance only 29 of 29+ files are displayed.

... ... @@ -27,7 +27,6 @@
#import "RMAbstractMercatorTileSource.h"
#import "RMTileImage.h"
#import "RMTileLoader.h"
#import "RMFractalTileProjection.h"
#import "RMTiledLayerController.h"
#import "RMProjection.h"
... ... @@ -87,7 +86,7 @@
return kDefaultLatLonBoundingBox;
}
- (UIImage *)imageForTileImage:(RMTileImage *)tileImage addToCache:(RMTileCache *)tileCache withCacheKey:(NSString *)aCacheKey
- (UIImage *)imageForTile:(RMTile)tile inCache:(RMTileCache *)tileCache
{
@throw [NSException exceptionWithName:@"RMAbstractMethodInvocation"
reason:@"imageForTile: invoked on AbstractMercatorWebSource. Override this method when instantiating abstract class."
... ...
... ... @@ -28,32 +28,7 @@
#import "RMAbstractMercatorTileSource.h"
#import "RMProjection.h"
@interface RMWebDownloadOperation : NSOperation {
RMTileImage *tileImage;
RMTileCache *tileCache;
NSString *cacheKey;
NSMutableData *data;
NSURL *tileURL;
NSURLConnection *connection;
NSUInteger retries;
BOOL isExecuting, isFinished;
}
@property (nonatomic, readonly) BOOL isExecuting;
@property (nonatomic, readonly) BOOL isFinished;
+ (id)operationWithUrl:(NSURL *)anURL withTileImage:(RMTileImage *)aTileImage andTileCache:(RMTileCache *)aTileCache withCacheKey:(NSString *)aCacheKey;
- (id)initWithUrl:(NSURL *)anURL withTileImage:(RMTileImage *)aTileImage andTileCache:(RMTileCache *)aTileCache withCacheKey:(NSString *)aCacheKey;
@end
#pragma mark -
@interface RMAbstractWebMapSource : RMAbstractMercatorTileSource {
NSOperationQueue *requestQueue;
}
- (NSURL *)URLForTile:(RMTile)tile;
... ...
... ... @@ -26,234 +26,47 @@
// POSSIBILITY OF SUCH DAMAGE.
#import "RMAbstractWebMapSource.h"
#import "RMTileImage.h"
#import "RMTileCache.h"
#import "RMTileImage.h"
#define kWebTileRetries 30
@implementation RMWebDownloadOperation
@synthesize isExecuting;
@synthesize isFinished;
+ (id)operationWithUrl:(NSURL *)anURL withTileImage:(RMTileImage *)aTileImage andTileCache:(RMTileCache *)aTileCache withCacheKey:(NSString *)aCacheKey
{
return [[[self alloc] initWithUrl:anURL withTileImage:aTileImage andTileCache:aTileCache withCacheKey:aCacheKey] autorelease];
}
@implementation RMAbstractWebMapSource
- (id)initWithUrl:(NSURL *)anURL withTileImage:(RMTileImage *)aTileImage andTileCache:(RMTileCache *)aTileCache withCacheKey:(NSString *)aCacheKey
- (id)init
{
if (!(self = [super init]))
return nil;
// [self setQueuePriority:10]; // Highest priority
connection = nil;
retries = kWebTileRetries;
data = [[NSMutableData alloc] initWithCapacity:0];
tileURL = [anURL retain];
tileImage = [aTileImage retain];
tileCache = [aTileCache retain];
cacheKey = [aCacheKey retain];
isExecuting = NO;
isFinished = NO;
return self;
}
- (void)dealloc
{
[connection cancel]; [connection release]; connection = nil;
[tileURL release]; tileURL = nil;
[data release]; data = nil;
[tileImage release]; tileImage = nil;
[tileCache release]; tileCache = nil;
[cacheKey release]; cacheKey = nil;
[super dealloc];
}
- (BOOL)isConcurrent
- (NSURL *)URLForTile:(RMTile)tile
{
return YES;
@throw [NSException exceptionWithName:@"RMAbstractMethodInvocation"
reason:@"URLForTile: invoked on AbstractMercatorWebSource. Override this method when instantiating abstract class."
userInfo:nil];
}
- (void)finish
- (UIImage *)imageForTile:(RMTile)tile inCache:(RMTileCache *)tileCache
{
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileRetrieved object:nil];
UIImage *image = nil;
[connection cancel]; [connection release]; connection = nil;
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
isExecuting = NO;
isFinished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
- (void)start
{
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
return;
}
tile = [[self mercatorToTileProjection] normaliseTile:tile];
image = [tileCache cachedImage:tile withCacheKey:[self uniqueTilecacheKey]];
if (image) return image;
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileRequested object:nil];
if (tileImage.loadingCancelled) {
[self finish];
return;
}
[self willChangeValueForKey:@"isExecuting"];
isExecuting = YES;
[self didChangeValueForKey:@"isExecuting"];
NSURLRequest *request = [NSURLRequest requestWithURL:tileURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30.0];
[connection cancel]; [connection release]; connection = nil;
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (!connection) {
[tileImage updateWithImage:[RMTileImage errorTile] andNotifyListeners:NO];
[self finish];
NSData *tileData = [NSData dataWithContentsOfURL:[self URLForTile:tile]];
if (tileData && [tileData length]) {
image = [UIImage imageWithData:tileData];
if (image) [tileCache addImage:image forTile:tile withCacheKey:[self uniqueTilecacheKey]];
}
}
- (void)connection:(NSURLConnection *)aConnection didReceiveResponse:(NSURLResponse *)aResponse
{
int statusCode = NSURLErrorUnknown; // unknown
if ([aResponse isKindOfClass:[NSHTTPURLResponse class]])
statusCode = [(NSHTTPURLResponse *)aResponse statusCode];
[data setLength:0];
if (statusCode < 400) { // Success
}
else if (statusCode == 404) { // Not Found
if (!tileImage.loadingCancelled)
[tileImage updateWithImage:[RMTileImage missingTile] andNotifyListeners:NO];
[self finish];
}
else { // Other Error
//RMLog(@"didReceiveResponse %@ %d", _connection, statusCode);
BOOL retry = FALSE;
switch(statusCode)
{
case 500: retry = TRUE; break;
case 503: retry = TRUE; break;
}
if (retry) {
[self start];
}
else {
[self finish];
}
}
}
- (void)connection:(NSURLConnection *)aConnection didReceiveData:(NSData *)newData
{
[data appendData:newData];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection
{
if ([data length] == 0) {
[self start];
}
else {
if (tileImage.loadingCancelled) {
[self finish];
return;
}
UIImage *image = [UIImage imageWithData:data];
[tileImage updateWithImage:image andNotifyListeners:YES];
if (tileCache) [tileCache addImage:image forTile:tileImage.tile withCacheKey:cacheKey];
[self finish];
}
}
- (void)connection:(NSURLConnection *)aConnection didFailWithError:(NSError *)error
{
//RMLog(@"didFailWithError %@ %d %@", _connection, [error code], [error localizedDescription]);
BOOL retry = FALSE;
switch ([error code])
{
case NSURLErrorBadURL: // -1000
case NSURLErrorTimedOut: // -1001
case NSURLErrorUnsupportedURL: // -1002
case NSURLErrorCannotFindHost: // -1003
case NSURLErrorCannotConnectToHost: // -1004
case NSURLErrorNetworkConnectionLost: // -1005
case NSURLErrorDNSLookupFailed: // -1006
case NSURLErrorResourceUnavailable: // -1008
case NSURLErrorNotConnectedToInternet: // -1009
retry = TRUE;
break;
}
if (retry) {
[self start];
}
else {
[self finish];
}
}
@end
#pragma mark -
@implementation RMAbstractWebMapSource
- (id)init
{
if (!(self = [super init]))
return nil;
requestQueue = [NSOperationQueue new];
[requestQueue setMaxConcurrentOperationCount:2];
return self;
}
- (void)dealloc
{
[requestQueue cancelAllOperations];
[requestQueue release]; requestQueue = nil;
[super dealloc];
}
- (NSURL *)URLForTile:(RMTile)tile
{
@throw [NSException exceptionWithName:@"RMAbstractMethodInvocation"
reason:@"URLForTile: invoked on AbstractMercatorWebSource. Override this method when instantiating abstract class."
userInfo:nil];
}
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileRetrieved object:nil];
- (UIImage *)imageForTileImage:(RMTileImage *)tileImage addToCache:(RMTileCache *)tileCache withCacheKey:(NSString *)aCacheKey
{
RMTile tile = [[self mercatorToTileProjection] normaliseTile:tileImage.tile];
if (!image) return [RMTileImage errorTile];
// [requestQueue setSuspended:YES];
// for (NSOperation *currentRequest in [requestQueue operations])
// {
// [currentRequest setQueuePriority:[currentRequest queuePriority] - 1];
// }
// [requestQueue setSuspended:NO];
[requestQueue addOperation:[RMWebDownloadOperation operationWithUrl:[self URLForTile:tile] withTileImage:tileImage andTileCache:tileCache withCacheKey:aCacheKey]];
return nil;
return image;
}
@end
... ...
... ... @@ -96,7 +96,7 @@
{
coordinate = aCoordinate;
self.projectedLocation = [[mapView projection] coordinateToProjectedPoint:aCoordinate];
self.position = [[mapView mercatorToScreenProjection] projectProjectedPoint:self.projectedLocation];
self.position = [mapView projectedPointToPixel:self.projectedLocation];
if (!self.hasBoundingBox)
self.projectedBoundingBox = RMProjectedRectMake(self.projectedLocation.x, self.projectedLocation.y, 1.0, 1.0);
... ... @@ -133,7 +133,7 @@
- (BOOL)isAnnotationWithinBounds:(CGRect)bounds
{
if (self.hasBoundingBox) {
RMProjectedRect projectedScreenBounds = [[mapView mercatorToScreenProjection] projectedBounds];
RMProjectedRect projectedScreenBounds = [mapView projectedBounds];
return RMProjectedRectIntersectsProjectedRect(projectedScreenBounds, projectedBoundingBox);
} else {
return CGRectContainsPoint(bounds, self.position);
... ... @@ -142,7 +142,7 @@
- (BOOL)isAnnotationOnScreen
{
CGRect screenBounds = [[mapView mercatorToScreenProjection] screenBounds];
CGRect screenBounds = [mapView bounds];
return [self isAnnotationWithinBounds:screenBounds];
}
... ...
... ... @@ -157,18 +157,18 @@
#pragma mark Map Movement and Scaling
- (void)moveBy:(CGSize)delta
{
if (enableDragging) {
[super moveBy:delta];
}
}
- (void)zoomByFactor:(float)zoomFactor near:(CGPoint)center
{
[super zoomByFactor:zoomFactor near:center];
[self updateCirclePath];
}
//- (void)moveBy:(CGSize)delta
//{
// if (enableDragging) {
// [super moveBy:delta];
// }
//}
//
//- (void)zoomByFactor:(float)zoomFactor near:(CGPoint)center
//{
// [super zoomByFactor:zoomFactor near:center];
// [self updateCirclePath];
//}
- (void)moveToCoordinate:(CLLocationCoordinate2D)newCoordinate
{
... ...
//
// RMCoreAnimationRenderer.h
//
// Copyright (c) 2008-2009, Route-Me Contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
#import "RMTileImageSet.h"
@class RMMapView;
@interface RMCoreAnimationRenderer : NSObject <RMTileImageSetDelegate> {
RMMapView *mapView;
CALayer *layer;
NSMutableArray *tiles;
}
@property (nonatomic, readonly) CALayer *layer;
@property (nonatomic, assign) CGRect frame;
- (id)initWithView:(RMMapView *)aMapView;
@end
//
// RMCoreAnimationRenderer.m
//
// Copyright (c) 2008-2009, Route-Me Contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#import <QuartzCore/QuartzCore.h>
#import "RMGlobalConstants.h"
#import "RMCoreAnimationRenderer.h"
#import "RMTile.h"
#import "RMTileLoader.h"
#import "RMPixel.h"
#import "RMTileImage.h"
#import "RMMapView.h"
@implementation RMCoreAnimationRenderer
- (id)initWithView:(RMMapView *)aMapView
{
if (!(self = [super init]))
return nil;
mapView = aMapView;
// NOTE: RMMapContents may still be initialising when this function
// is called. Be careful using any of the methods - they might return
// strange data.
layer = [[CAScrollLayer layer] retain];
layer.anchorPoint = CGPointZero;
layer.masksToBounds = YES;
// If the frame is set incorrectly here, it will be fixed when setRenderer is called in RMMapContents
layer.frame = [mapView screenBounds];
NSMutableDictionary *customActions = [NSMutableDictionary dictionaryWithDictionary:[layer actions]];
[customActions setObject:[NSNull null] forKey:@"sublayers"];
layer.actions = customActions;
layer.delegate = self;
tiles = [[NSMutableArray alloc] init];
return self;
}
- (void)dealloc
{
[tiles release]; tiles = nil;
[layer release]; layer = nil;
[super dealloc];
}
- (void)tileImageAdded:(RMTileImage *)image
{
// RMLog(@"tileAdded: %d %d %d at %f %f %f %f", tile.x, tile.y, tile.zoom, image.screenLocation.origin.x, image.screenLocation.origin.y,
// image.screenLocation.size.width, image.screenLocation.size.height);
// RMLog(@"tileAdded");
@synchronized (tiles) {
RMTile tile = image.tile;
NSUInteger min = 0, max = [tiles count];
CALayer *sublayer = [image layer];
sublayer.delegate = self;
while (min < max) {
// Binary search for insertion point
NSUInteger pivot = (min + max) / 2;
RMTileImage *other = [tiles objectAtIndex:pivot];
RMTile otherTile = other.tile;
if (otherTile.zoom <= tile.zoom) {
min = pivot + 1;
}
if (otherTile.zoom > tile.zoom) {
max = pivot;
}
}
[tiles insertObject:image atIndex:min];
[layer insertSublayer:sublayer atIndex:min];
}
}
- (void)tileImageRemoved:(RMTileImage *)tileImage
{
@synchronized (tiles) {
RMTileImage *image = nil;
RMTile tile = tileImage.tile;
for (NSInteger i = [tiles count]-1; i>=0; --i)
{
RMTileImage *potential = [tiles objectAtIndex:i];
if (RMTilesEqual(tile, potential.tile))
{
image = [[potential retain] autorelease];
[tiles removeObjectAtIndex:i];
break;
}
}
// RMLog(@"tileRemoved: %d %d %d at %f %f %f %f", tile.x, tile.y, tile.zoom, image.screenLocation.origin.x, image.screenLocation.origin.y,
// image.screenLocation.size.width, image.screenLocation.size.height);
[[image layer] removeFromSuperlayer];
}
}
- (CGRect)frame
{
return layer.frame;
}
// \bug ??? frame is always set to the screen bounds?
- (void)setFrame:(CGRect)frame
{
layer.frame = [mapView screenBounds];
}
- (CALayer *)layer
{
return layer;
}
@end
... ... @@ -190,11 +190,13 @@
#pragma mark RMTileSource methods
- (UIImage *)imageForTileImage:(RMTileImage *)tileImage addToCache:(RMTileCache *)tileCache withCacheKey:(NSString *)aCacheKey
- (UIImage *)imageForTile:(RMTile)tile inCache:(RMTileCache *)tileCache
{
UIImage *image = nil;
RMTile tile = [[self mercatorToTileProjection] normaliseTile:tileImage.tile];
tile = [[self mercatorToTileProjection] normaliseTile:tile];
image = [tileCache cachedImage:tile withCacheKey:[self uniqueTilecacheKey]];
if (image) return image;
// get the unique key for the tile
NSNumber *key = [NSNumber numberWithLongLong:RMTileKey(tile)];
... ... @@ -212,8 +214,8 @@
[result close];
}
if (tileCache)
[tileCache addImage:image forTile:tile withCacheKey:aCacheKey];
if (image)
[tileCache addImage:image forTile:tile withCacheKey:[self uniqueTilecacheKey]];
return image;
}
... ...
... ... @@ -30,6 +30,10 @@
#include <stdbool.h>
#if __OBJC__
#import <CoreLocation/CoreLocation.h>
#endif
/*! \struct RMProjectedPoint
\brief coordinates, in projected meters, paralleling CGPoint */
typedef struct {
... ... @@ -49,6 +53,15 @@ typedef struct {
RMProjectedSize size;
} RMProjectedRect;
#if __OBJC__
/*! \struct RMSphericalTrapezium
\brief a rectangle, specified by two corner coordinates */
typedef struct {
CLLocationCoordinate2D southWest;
CLLocationCoordinate2D northEast;
} RMSphericalTrapezium;
#endif
RMProjectedPoint RMScaleProjectedPointAboutPoint(RMProjectedPoint point, float factor, RMProjectedPoint pivot);
RMProjectedRect RMScaleProjectedRectAboutPoint(RMProjectedRect rect, float factor, RMProjectedPoint pivot);
RMProjectedPoint RMTranslateProjectedPointBy(RMProjectedPoint point, RMProjectedSize delta);
... ...
... ... @@ -7,19 +7,12 @@
*
*/
#import <CoreLocation/CoreLocation.h>
#ifndef _GLOBAL_CONSTANTS_H_
#define _GLOBAL_CONSTANTS_H_
#define kMaxLong 180
#define kMaxLat 90
typedef struct {
CLLocationCoordinate2D southWest;
CLLocationCoordinate2D northEast;
} RMSphericalTrapezium;
static const double kRMMinLatitude = -kMaxLat;
static const double kRMMaxLatitude = kMaxLat;
static const double kRMMinLongitude = -kMaxLong;
... ...
... ... @@ -52,7 +52,4 @@
@property (nonatomic, assign) BOOL enableRotation;
@property (nonatomic, retain) id userInfo;
- (void)moveBy:(CGSize)delta;
- (void)zoomByFactor:(float)zoomFactor near:(CGPoint)center;
@end
... ...
//
// RMMapOverlayView.h
// MapView
//
// Created by Thomas Rasch on 25.08.11.
// Copyright (c) 2011 Alpstein. All rights reserved.
//
#import <UIKit/UIKit.h>
@class RMMapOverlayView, RMAnnotation;
@protocol RMMapOverlayViewDelegate <NSObject>
@optional
- (void)mapOverlayView:(RMMapOverlayView *)aMapOverlayView tapOnAnnotation:(RMAnnotation *)anAnnotation;
- (void)mapOverlayView:(RMMapOverlayView *)aMapOverlayView tapOnLabelForAnnotation:(RMAnnotation *)anAnnotation;
@end
@interface RMMapOverlayView : UIView {
id <RMMapOverlayViewDelegate> delegate;
}
@property (nonatomic, assign) id <RMMapOverlayViewDelegate> delegate;
- (unsigned)sublayersCount;
- (void)addSublayer:(CALayer *)aLayer;
- (void)insertSublayer:(CALayer *)aLayer atIndex:(unsigned)index;
- (void)insertSublayer:(CALayer *)aLayer below:(CALayer *)sublayer;
- (void)insertSublayer:(CALayer *)aLayer above:(CALayer *)sublayer;
@end
... ...
//
// RMMapOverlayView.m
// MapView
//
// Created by Thomas Rasch on 25.08.11.
// Copyright (c) 2011 Alpstein. All rights reserved.
//
#import "RMMapOverlayView.h"
#import "RMMarker.h"
@interface RMMapOverlayView ()
- (void)handleSingleTap:(UIGestureRecognizer *)recognizer;
@end
@implementation RMMapOverlayView
@synthesize delegate;
- (id)initWithFrame:(CGRect)frame
{
if (!(self = [super initWithFrame:frame]))
return nil;
self.layer.masksToBounds = YES;
UITapGestureRecognizer *singleTapRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)] autorelease];
[self addGestureRecognizer:singleTapRecognizer];
return self;
}
- (unsigned)sublayersCount
{
return [self.layer.sublayers count];
}
- (void)addSublayer:(CALayer *)aLayer
{
[self.layer addSublayer:aLayer];
}
- (void)insertSublayer:(CALayer *)aLayer atIndex:(unsigned)index
{
[self.layer insertSublayer:aLayer atIndex:index];
}
- (void)insertSublayer:(CALayer *)aLayer below:(CALayer *)sublayer
{
[self.layer insertSublayer:aLayer below:sublayer];
}
- (void)insertSublayer:(CALayer *)aLayer above:(CALayer *)sublayer
{
[self.layer insertSublayer:aLayer above:sublayer];
}
#pragma mark -
#pragma mark Event handling
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if ([[event touchesForView:self] count] > 1)
return NO;
CALayer *hit = [self.layer hitTest:point];
if (!hit || ![hit isKindOfClass:[RMMarker class]]) {
return NO;
}
return YES;
}
- (void)handleSingleTap:(UIGestureRecognizer *)recognizer
{
CALayer *hit = [self.layer hitTest:[recognizer locationInView:self]];
RMLog(@"LAYER of type %@",[hit description]);
if (hit != nil)
{
CALayer *superlayer = [hit superlayer];
// See if tap was on a marker or marker label and send delegate protocol method
if ([hit isKindOfClass:[RMMarker class]]) {
if ([delegate respondsToSelector:@selector(mapOverlayView:tapOnAnnotation:)]) {
[delegate mapOverlayView:self tapOnAnnotation:[((RMMarker *)hit) annotation]];
}
} else if (superlayer != nil && [superlayer isKindOfClass:[RMMarker class]]) {
if ([delegate respondsToSelector:@selector(mapOverlayView:tapOnLabelForAnnotation:)]) {
[delegate mapOverlayView:self tapOnLabelForAnnotation:[((RMMarker *)superlayer) annotation]];
}
} else if ([superlayer superlayer] != nil && [[superlayer superlayer] isKindOfClass:[RMMarker class]]) {
if ([delegate respondsToSelector:@selector(mapOverlayView:tapOnLabelForAnnotation:)]) {
[delegate mapOverlayView:self tapOnLabelForAnnotation:[((RMMarker *)[superlayer superlayer]) annotation]];
}
}
}
}
@end
... ...
//
// RMMapTiledLayerView.h
// MapView
//
// Created by Thomas Rasch on 17.08.11.
// Copyright (c) 2011 Alpstein. All rights reserved.
//
@class RMMapView, RMMapTiledLayerView;
@protocol RMMapTiledLayerViewDelegate <NSObject>
@optional
// points are in the mapview coordinate space
- (void)mapTiledLayerView:(RMMapTiledLayerView *)aTiledLayerView singleTapAtPoint:(CGPoint)aPoint;
- (void)mapTiledLayerView:(RMMapTiledLayerView *)aTiledLayerView doubleTapAtPoint:(CGPoint)aPoint;
- (void)mapTiledLayerView:(RMMapTiledLayerView *)aTiledLayerView twoFingerDoubleTapAtPoint:(CGPoint)aPoint;
- (void)mapTiledLayerView:(RMMapTiledLayerView *)aTiledLayerView longPressAtPoint:(CGPoint)aPoint;
@end
@interface RMMapTiledLayerView : UIView {
id <RMMapTiledLayerViewDelegate> delegate;
RMMapView *mapView;
}
@property (nonatomic, assign) id <RMMapTiledLayerViewDelegate> delegate;
- (id)initWithFrame:(CGRect)frame mapView:(RMMapView *)aMapView;
@end
... ...
//
// RMMapTiledLayerView.m
// MapView
//
// Created by Thomas Rasch on 17.08.11.
// Copyright (c) 2011 Alpstein. All rights reserved.
//
#import "RMMapTiledLayerView.h"
#import "RMMapView.h"
#import "RMTileSource.h"
@interface RMMapOverlayView ()
- (void)handleDoubleTap:(UIGestureRecognizer *)recognizer;
- (void)handleTwoFingerDoubleTap:(UIGestureRecognizer *)recognizer;
@end
@implementation RMMapTiledLayerView
@synthesize delegate;
+ + layerClass
{
return [CATiledLayer class];
}
- (CATiledLayer *)tiledLayer
{
return (CATiledLayer *)self.layer;
}
- (id)initWithFrame:(CGRect)frame mapView:(RMMapView *)aMapView
{
if (!(self = [super initWithFrame:frame]))
return nil;
mapView = aMapView;
self.userInteractionEnabled = YES;
self.multipleTouchEnabled = YES;
CATiledLayer *tiledLayer = [self tiledLayer];
tiledLayer.levelsOfDetailBias = [[mapView tileSource] maxZoom] - 1;
tiledLayer.levelsOfDetail = [[mapView tileSource] maxZoom] - 1;
UITapGestureRecognizer *doubleTapRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)] autorelease];
doubleTapRecognizer.numberOfTapsRequired = 2;
UITapGestureRecognizer *singleTapRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)] autorelease];
[singleTapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];
UITapGestureRecognizer *twoFingerDoubleTapRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTwoFingerDoubleTap:)] autorelease];
twoFingerDoubleTapRecognizer.numberOfTapsRequired = 2;
twoFingerDoubleTapRecognizer.numberOfTouchesRequired = 2;
UILongPressGestureRecognizer *longPressRecognizer = [[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)] autorelease];
[self addGestureRecognizer:singleTapRecognizer];
[self addGestureRecognizer:doubleTapRecognizer];
[self addGestureRecognizer:twoFingerDoubleTapRecognizer];
[self addGestureRecognizer:longPressRecognizer];
return self;
}
- (void)layoutSubviews
{
self.contentScaleFactor = 1.0f;
}
-(void)drawRect:(CGRect)rect
{
CGRect bounds = self.bounds;
// NSLog(@"drawRect: {{%.0f,%.0f},{%.2f,%.2f}}", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
short zoom = log2(bounds.size.width / rect.size.width);
int x = floor(rect.origin.x / rect.size.width), y = floor(fabs(rect.origin.y / rect.size.height));
// NSLog(@"Tile @ x:%d, y:%d, zoom:%d", x, y, zoom);
UIImage *tileImage = [[mapView tileSource] imageForTile:RMTileMake(x, y, zoom) inCache:[mapView tileCache]];
[tileImage drawInRect:rect];
}
#pragma mark -
#pragma mark Event handling
- (void)handleSingleTap:(UIGestureRecognizer *)recognizer
{
if ([delegate respondsToSelector:@selector(mapTiledLayerView:singleTapAtPoint:)])
[delegate mapTiledLayerView:self singleTapAtPoint:[recognizer locationInView:mapView]];
}
- (void)handleLongPress:(UILongPressGestureRecognizer *)recognizer
{
if ([delegate respondsToSelector:@selector(mapTiledLayerView:longPressAtPoint:)])
[delegate mapTiledLayerView:self longPressAtPoint:[recognizer locationInView:mapView]];
}
- (void)handleDoubleTap:(UIGestureRecognizer *)recognizer
{
if ([delegate respondsToSelector:@selector(mapTiledLayerView:doubleTapAtPoint:)])
[delegate mapTiledLayerView:self doubleTapAtPoint:[recognizer locationInView:mapView]];
}
- (void)handleTwoFingerDoubleTap:(UIGestureRecognizer *)recognizer
{
if ([delegate respondsToSelector:@selector(mapTiledLayerView:twoFingerDoubleTapAtPoint:)])
[delegate mapTiledLayerView:self twoFingerDoubleTapAtPoint:[recognizer locationInView:mapView]];
}
@end
... ...
... ... @@ -25,85 +25,16 @@
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
/*! \mainpage Route-Me Map Framework
\section intro_sec Introduction
Route-Me is an open source Objective-C framework for displaying maps on Cocoa Touch devices
(the iPhone, and the iPod Touch). It was written in 2008 by Joseph Gentle as the basis for a transit
routing app. The transit app was not completed, because the government agencies involved chose not to release
the necessary data under reasonable licensing terms. The project was released as open source under the New BSD license (http://www.opensource.org/licenses/bsd-license.php)
in September, 2008, and
is hosted on Google Code (http://code.google.com/p/route-me/).
Route-Me provides a UIView subclass with a panning, zooming map. Zoom level, source of map data, map center,
marker overlays, and path overlays are all supported.
\section license_sec License
Route-Me is licensed under the New BSD license.
In any app that uses the Route-Me library, include the following text on your "preferences" or "about" screen: "Uses Route-Me map library, (c) 2008-2009 Route-Me Contributors".
\section install_sec Installation
Because Route-Me is under rapid development as of early 2009, the best way to install Route-Me is use
Subversion and check out a copy of the repository:
\verbatim
svn checkout http://route-me.googlecode.com/svn/trunk/ route-me-read-only
\endverbatim
There are numerous sample applications in the Subversion repository.
To embed Route-Me maps in your Xcode project, follow the example given in samples/SampleMap or samples/ProgrammaticMap. The instructions in
the Embedding Guide at
http://code.google.com/p/route-me/wiki/EmbeddingGuide are out of date as of April 20, 2009. To create a static version of Route-Me, follow these
instructions instead: http://code.google.com/p/route-me/source/browse/trunk/MapView/README-library-build.rtf
\section maps_sec Map Data
Route-Me supports map data served from many different sources:
- the Open Street Map project's server.
- CloudMade, which provides commercial servers delivering Open Street Map data.
- Microsoft Virtual Earth.
- Open Aerial Map.
- Yahoo! Maps.
Each of these data sources has different license restrictions and fees. In particular, Yahoo! Maps are
effectively unusable in Route-Me due to their license terms; the Yahoo! access code is provided for demonstration
purposes only.
You must contact the data vendor directly and arrange licensing if necessary, including obtaining your own
access key. Follow their rules.
If you have your own data you'd like to use with Route-Me, serving it through your own Mapnik installation
looks like the best bet. Mapnik is an open source web-based map delivery platform. For more information on
Mapnik, see http://www.mapnik.org/ .
\section news_sec Project News and Updates
For the most current information on Route-Me, see these sources:
- wiki: http://code.google.com/p/route-me/w/list
- project email reflector: http://groups.google.com/group/route-me-map
- list of all project RSS feeds: http://code.google.com/p/route-me/feeds
- applications using Route-Me: http://code.google.com/p/route-me/wiki/RoutemeApplications
*/
#import <UIKit/UIKit.h>
#import <CoreGraphics/CGGeometry.h>
#import "RMGlobalConstants.h"
#import "RMNotifications.h"
#import "RMFoundation.h"
#import "RMMapViewDelegate.h"
#import "RMTile.h"
#import "RMProjection.h"
// iPhone-specific mapview stuff. Handles event handling, whatnot.
typedef struct {
CGPoint center;
CGFloat angle;
float averageDistanceFromCenter;
int numTouches;
} RMGestureDetails;
#import "RMMapOverlayView.h"
#import "RMMapTiledLayerView.h"
// constants for boundingMask
enum {
... ... @@ -112,42 +43,40 @@ enum {
RMMapMinWidthBound = 2 // Minimum map width when zooming out restricted to view width (default)
};
@class RMMarkerManager;
typedef enum {
RMMapDecelerationNormal,
RMMapDecelerationFast
} RMMapDecelerationMode;
@class RMProjection;
@class RMMercatorToScreenProjection;
@class RMTileCache;
@class RMTileImageSet;
@class RMTileLoader;
@class RMCoreAnimationRenderer;
@class RMMapLayer;
@class RMMarkerLayer;
@class RMMapTiledLayerView;
@class RMMarker;
@class RMAnnotation;
@class RMQuadTree;
@protocol RMMercatorToTileProjection;
@protocol RMTileSource;
@protocol RMMapTiledLayerViewDelegate;
@interface RMMapView : UIView
@interface RMMapView : UIView <UIScrollViewDelegate, RMMapOverlayViewDelegate, RMMapTiledLayerViewDelegate>
{
id <RMMapViewDelegate> delegate;
BOOL enableDragging;
BOOL enableZoom;
BOOL deceleration;
float decelerationFactor;
RMGestureDetails lastGesture;
/// projection objects to convert from latitude/longitude to meters,
/// from projected meters to tiles and screen coordinates
RMProjection *projection;
id <RMMercatorToTileProjection> mercatorToTileProjection;
RMMercatorToScreenProjection *mercatorToScreenProjection;
RMMapLayer *overlay; /// subview for markers and paths
RMCoreAnimationRenderer *renderer;
RMTileImageSet *imagesOnScreen;
RMTileLoader *tileLoader;
/// subview for the background image displayed while tiles are loading. Set its contents by providing your own "loading.png".
UIView *backgroundView;
UIScrollView *mapScrollView;
RMMapTiledLayerView *tiledLayerView;
RMMapOverlayView *overlayView;
double metersPerPixel;
NSMutableArray *annotations;
NSMutableSet *visibleAnnotations;
... ... @@ -158,9 +87,6 @@ enum {
id <RMTileSource> tileSource;
RMTileCache *tileCache; // Generic tile cache
/// subview for the image displayed while tiles are loading. Set its contents by providing your own "loading.png".
CALayer *background;
/// minimum and maximum zoom number allowed for the view. #minZoom and #maxZoom must be within the limits of #tileSource but can be stricter; they are clamped to tilesource limits if needed.
float minZoom;
float maxZoom;
... ... @@ -172,18 +98,15 @@ enum {
@private
BOOL _delegateHasBeforeMapMove;
BOOL _delegateHasAfterMapMove;
BOOL _delegateHasAfterMapMoveDeceleration;
BOOL _delegateHasBeforeMapZoomByFactor;
BOOL _delegateHasAfterMapZoomByFactor;
BOOL _delegateHasBeforeMapZoom;
BOOL _delegateHasAfterMapZoom;
BOOL _delegateHasMapViewRegionDidChange;
BOOL _delegateHasBeforeMapRotate;
BOOL _delegateHasAfterMapRotate;
BOOL _delegateHasDoubleTapOnMap;
BOOL _delegateHasDoubleTapTwoFingersOnMap;
BOOL _delegateHasSingleTapOnMap;
BOOL _delegateHasLongSingleTapOnMap;
BOOL _delegateHasTapOnMarker;
BOOL _delegateHasTapOnLabelForMarker;
BOOL _delegateHasTapOnAnnotation;
BOOL _delegateHasTapOnLabelForAnnotation;
BOOL _delegateHasAfterMapTouch;
BOOL _delegateHasShouldDragMarker;
BOOL _delegateHasDidDragMarker;
... ... @@ -191,14 +114,6 @@ enum {
BOOL _delegateHasWillHideLayerForAnnotation;
BOOL _delegateHasDidHideLayerForAnnotation;
NSTimer *_decelerationTimer;
CGSize _decelerationDelta;
CGPoint _longPressPosition;
NSTimer *_moveAnimationTimer;
RMProjectedPoint _moveAnimationStartPoint, _moveAnimationEndPoint;
double _moveAnimationCurrentStep, _moveAnimationSteps;
BOOL _constrainMovement;
RMProjectedPoint _northEastConstraint, _southWestConstraint;
}
... ... @@ -207,18 +122,13 @@ enum {
// View properties
@property (nonatomic, assign) BOOL enableDragging;
@property (nonatomic, assign) BOOL enableZoom;
@property (nonatomic, assign) BOOL deceleration;
@property (nonatomic, assign) float decelerationFactor;
@property (nonatomic, readonly) RMGestureDetails lastGesture;
@property (nonatomic, assign) RMMapDecelerationMode decelerationMode;
@property (nonatomic, assign) CLLocationCoordinate2D mapCenterCoordinate;
@property (nonatomic, assign) RMProjectedPoint mapCenterProjectedPoint;
@property (nonatomic, assign) RMProjectedRect projectedBounds;
@property (nonatomic, readonly) RMTileRect tileBounds;
@property (nonatomic, readonly) CGRect screenBounds;
@property (nonatomic, assign) float metersPerPixel;
@property (nonatomic, readonly) float scaledMetersPerPixel;
@property (nonatomic, assign) double metersPerPixel;
@property (nonatomic, readonly) double scaledMetersPerPixel;
@property (nonatomic, readonly) double scaleDenominator; /// The denominator in a cartographic scale like 1/24000, 1/50000, 1/2000000.
@property (nonatomic, readonly) float screenScale;
@property (nonatomic, assign) NSUInteger boundingMask;
... ... @@ -227,30 +137,18 @@ enum {
@property (nonatomic, assign) float minZoom;
@property (nonatomic, assign) float maxZoom;
@property (nonatomic, readonly) RMMarkerManager *markerManager;
@property (nonatomic, readonly) RMMapLayer *overlay;
@property (nonatomic, retain) RMQuadTree *quadTree;
@property (nonatomic, assign) BOOL enableClustering;
@property (nonatomic, assign) BOOL positionClusterMarkersAtTheGravityCenter;
@property (nonatomic, assign) CGSize clusterMarkerSize;
@property (nonatomic, readonly) RMTileImageSet *imagesOnScreen;
@property (nonatomic, readonly) RMTileLoader *tileLoader;
@property (nonatomic, retain) RMCoreAnimationRenderer *renderer;
@property (nonatomic, readonly) RMProjection *projection;
@property (nonatomic, readonly) id <RMMercatorToTileProjection> mercatorToTileProjection;
@property (nonatomic, readonly) RMMercatorToScreenProjection *mercatorToScreenProjection;
@property (nonatomic, retain) id <RMTileSource> tileSource;
@property (nonatomic, retain) RMTileCache *tileCache;
@property (nonatomic, retain) CALayer *background;
// tileDepth defaults to zero. if tiles have no alpha, set this higher, 3 or so, to make zooming smoother
@property (nonatomic, assign) short tileDepth;
@property (nonatomic, readonly) BOOL fullyLoaded;
@property (nonatomic, retain) UIView *backgroundView;
#pragma mark -
#pragma mark Initializers
... ... @@ -277,15 +175,18 @@ enum {
/// recenter the map on #aPoint, expressed in projected meters
- (void)moveToProjectedPoint:(RMProjectedPoint)aPoint;
- (void)moveToProjectedPoint:(RMProjectedPoint)aPoint animated:(BOOL)animated;
- (void)moveBy:(CGSize)delta;
- (void)setConstraintsSouthWest:(CLLocationCoordinate2D)sw northEeast:(CLLocationCoordinate2D)ne;
- (void)setProjectedConstraintsSouthWest:(RMProjectedPoint)sw northEast:(RMProjectedPoint)ne;
- (void)setConstraintsSouthWest:(CLLocationCoordinate2D)southWest northEeast:(CLLocationCoordinate2D)northEast;
- (void)setProjectedConstraintsSouthWest:(RMProjectedPoint)southWest northEast:(RMProjectedPoint)northEast;
#pragma mark -
#pragma mark Zoom
- (void)setProjectedBounds:(RMProjectedRect)boundsRect animated:(BOOL)animated;
- (void)zoomByFactor:(float)zoomFactor near:(CGPoint)aPoint;
- (void)zoomByFactor:(float)zoomFactor near:(CGPoint)center animated:(BOOL)animated;
... ... @@ -294,27 +195,29 @@ enum {
- (void)zoomOutToNextNativeZoomAt:(CGPoint)pivot;
- (void)zoomOutToNextNativeZoomAt:(CGPoint)pivot animated:(BOOL)animated;
- (void)zoomWithLatitudeLongitudeBoundsSouthWest:(CLLocationCoordinate2D)sw northEast:(CLLocationCoordinate2D)ne;
- (void)zoomWithProjectedBounds:(RMProjectedRect)bounds;
- (void)zoomWithLatitudeLongitudeBoundsSouthWest:(CLLocationCoordinate2D)southWest northEast:(CLLocationCoordinate2D)northEast;
- (void)zoomWithLatitudeLongitudeBoundsSouthWest:(CLLocationCoordinate2D)southWest northEast:(CLLocationCoordinate2D)northEast animated:(BOOL)animated;
- (float)nextNativeZoomFactor;
- (float)previousNativeZoomFactor;
- (float)adjustedZoomForCurrentBoundingMask:(float)zoomFactor;
#pragma mark -
#pragma mark Conversions
- (CGPoint)projectedPointToPixel:(RMProjectedPoint)projectedPoint;
- (CGPoint)coordinateToPixel:(CLLocationCoordinate2D)coordinate;
- (CGPoint)coordinateToPixel:(CLLocationCoordinate2D)coordinate withMetersPerPixel:(float)aScale;
- (RMTilePoint)coordinateToTilePoint:(CLLocationCoordinate2D)coordinate withMetersPerPixel:(float)aScale;
- (CLLocationCoordinate2D)pixelToCoordinate:(CGPoint)aPixel;
- (CLLocationCoordinate2D)pixelToCoordinate:(CGPoint)aPixel withMetersPerPixel:(float)aScale;
/// returns the smallest bounding box containing the entire screen
- (RMSphericalTrapezium)latitudeLongitudeBoundingBoxForScreen;
/// returns the smallest bounding box containing a rectangular region of the screen
- (RMProjectedPoint)pixelToProjectedPoint:(CGPoint)pixelCoordinate;
- (CLLocationCoordinate2D)pixelToCoordinate:(CGPoint)pixelCoordinate;
/// returns the smallest bounding box containing the entire view
- (RMSphericalTrapezium)latitudeLongitudeBoundingBox;
/// returns the smallest bounding box containing a rectangular region of the view
- (RMSphericalTrapezium)latitudeLongitudeBoundingBoxFor:(CGRect) rect;
#pragma mark -
#pragma mark Bounds
- (BOOL)projectedBounds:(RMProjectedRect)bounds containsPoint:(RMProjectedPoint)point;
- (BOOL)tileSourceBoundsContainProjectedPoint:(RMProjectedPoint)point;
... ...
... ... @@ -29,10 +29,6 @@
#import "RMMapViewDelegate.h"
#import "RMPixel.h"
#import "RMTileLoader.h"
#import "RMMercatorToScreenProjection.h"
#import "RMMarker.h"
#import "RMFoundation.h"
#import "RMProjection.h"
#import "RMMarker.h"
... ... @@ -40,33 +36,22 @@
#import "RMAnnotation.h"
#import "RMQuadTree.h"
#import "RMMercatorToScreenProjection.h"
#import "RMMercatorToTileProjection.h"
#import "RMOpenStreetMapSource.h"
#import "RMTileCache.h"
#import "RMTileSource.h"
#import "RMTileLoader.h"
#import "RMTileImageSet.h"
#import "RMCoreAnimationRenderer.h"
#import "RMMapTiledLayerView.h"
#import "RMMapOverlayView.h"
#pragma mark --- begin constants ----
#define kDefaultDecelerationFactor .80f
#define kMinDecelerationDelta 0.6f
#define kDecelerationTimerInterval 0.04f
#define kZoomAnimationStepTime 0.03f
#define kZoomAnimationAnimationTime 0.1f
#define kiPhoneMilimeteresPerPixel .1543
#define kZoomRectPixelBuffer 50
#define kMoveAnimationDuration 0.5f
#define kMoveAnimationStepDuration 0.04f
#define kDefaultInitialLatitude -33.858771
#define kDefaultInitialLongitude 151.201596
#define kDefaultInitialLatitude 47.56
#define kDefaultInitialLongitude 10.22
#define kDefaultMinimumZoomLevel 0.0
#define kDefaultMaximumZoomLevel 25.0
... ... @@ -78,14 +63,7 @@
@property (nonatomic, retain) RMMapLayer *overlay;
// methods for post-touch deceleration
- (void)startDecelerationWithDelta:(CGSize)delta;
- (void)incrementDeceleration:(NSTimer *)timer;
- (void)stopDeceleration;
- (void)stopMoveAnimation;
- (void)animationFinishedWithZoomFactor:(float)zoomFactor near:(CGPoint)p;
- (void)animationStepped;
- (void)recreateMapView;
- (void)correctPositionOfAllAnnotations;
- (void)correctPositionOfAllAnnotationsIncludingInvisibles:(BOOL)correctAllLayers;
... ... @@ -96,16 +74,11 @@
@implementation RMMapView
@synthesize decelerationFactor, deceleration;
@synthesize enableDragging, enableZoom;
@synthesize lastGesture;
@synthesize enableDragging, decelerationMode;
@synthesize boundingMask;
@synthesize minZoom, maxZoom;
@synthesize screenScale;
@synthesize markerManager;
@synthesize imagesOnScreen;
@synthesize tileCache;
@synthesize quadTree;
@synthesize enableClustering, positionClusterMarkersAtTheGravityCenter, clusterMarkerSize;
... ... @@ -121,24 +94,17 @@
backgroundImage:(UIImage *)backgroundImage
{
enableDragging = YES;
enableZoom = YES;
decelerationFactor = kDefaultDecelerationFactor;
deceleration = NO;
if (enableZoom)
[self setMultipleTouchEnabled:TRUE];
_constrainMovement = NO;
self.backgroundColor = [UIColor grayColor];
_constrainMovement = NO;
tileSource = nil;
projection = nil;
mercatorToTileProjection = nil;
renderer = nil;
imagesOnScreen = nil;
tileLoader = nil;
mapScrollView = nil;
tiledLayerView = nil;
overlayView = nil;
screenScale = 1.0;
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)])
{
... ... @@ -147,8 +113,6 @@
boundingMask = RMMapMinWidthBound;
mercatorToScreenProjection = [[RMMercatorToScreenProjection alloc] initFromProjection:[newTilesource projection] toScreenBounds:[self bounds]];
annotations = [NSMutableArray new];
visibleAnnotations = [NSMutableSet new];
[self setQuadTree:[[[RMQuadTree alloc] initWithMapView:self] autorelease]];
... ... @@ -157,32 +121,27 @@
[self setTileCache:[[[RMTileCache alloc] init] autorelease]];
[self setTileSource:newTilesource];
[self setRenderer:[[[RMCoreAnimationRenderer alloc] initWithView:self] autorelease]];
imagesOnScreen = [[RMTileImageSet alloc] initWithDelegate:renderer];
[imagesOnScreen setTileSource:tileSource];
[imagesOnScreen setTileCache:tileCache];
[imagesOnScreen setCurrentCacheKey:[newTilesource uniqueTilecacheKey]];
tileLoader = [[RMTileLoader alloc] initWithView:self];
[tileLoader setSuppressLoading:YES];
[self setBackgroundView:[[[UIView alloc] initWithFrame:[self bounds]] autorelease]];
if (backgroundImage)
self.backgroundView.layer.contents = (id)backgroundImage.CGImage;
if (minZoomLevel < newTilesource.minZoom) minZoomLevel = newTilesource.minZoom;
if (maxZoomLevel > newTilesource.maxZoom) maxZoomLevel = newTilesource.maxZoom;
[self setMinZoom:minZoomLevel];
[self setMaxZoom:maxZoomLevel];
[self setZoom:initialZoomLevel];
[self moveToCoordinate:initialCenterCoordinate];
[tileLoader setSuppressLoading:NO];
[self setBackground:[[[CALayer alloc] init] autorelease]];
[self setOverlay:[[[RMMapLayer alloc] init] autorelease]];
[self recreateMapView];
RMProjectedPoint initialProjectedCenter = [projection coordinateToProjectedPoint:initialCenterCoordinate];
[self setMapCenterProjectedPoint:initialProjectedCenter];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleMemoryWarningNotification:)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
RMLog(@"Map initialised. tileSource:%@, renderer:%@, minZoom:%.0f, maxZoom:%.0f", tileSource, renderer, [self minZoom], [self maxZoom]);
RMLog(@"Map initialised. tileSource:%@, minZoom:%.0f, maxZoom:%.0f, zoom:%.0f", tileSource, [self minZoom], [self maxZoom], [self zoom]);
}
- (id)initWithCoder:(NSCoder *)aDecoder
... ... @@ -255,14 +214,11 @@
[super setFrame:frame];
// only change if the frame changes and not during initialization
if (tileLoader && !CGRectEqualToRect(r, frame)) {
if (!CGRectEqualToRect(r, frame)) {
CGRect bounds = CGRectMake(0, 0, frame.size.width, frame.size.height);
[mercatorToScreenProjection setScreenBounds:bounds];
background.frame = bounds;
overlay.frame = bounds;
[renderer setFrame:bounds];
[tileLoader clearLoadedBounds];
[tileLoader updateLoadedImages];
backgroundView.frame = bounds;
mapScrollView.frame = bounds;
overlayView.frame = bounds;
[self correctPositionOfAllAnnotations];
}
}
... ... @@ -272,18 +228,15 @@
LogMethod();
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self setDelegate:nil];
[self stopDeceleration];
[imagesOnScreen cancelLoading];
[self setRenderer:nil];
[mapScrollView release]; mapScrollView = nil;
[tiledLayerView release]; tiledLayerView = nil;
[overlayView release]; overlayView = nil;
[self setTileCache:nil];
[imagesOnScreen release]; imagesOnScreen = nil;
[tileLoader release]; tileLoader = nil;
[projection release]; projection = nil;
[mercatorToTileProjection release]; mercatorToTileProjection = nil;
[mercatorToScreenProjection release]; mercatorToScreenProjection = nil;
[tileSource release]; tileSource = nil;
[self setOverlay:nil];
[self setBackground:nil];
[self setBackgroundView:nil];
[annotations release]; annotations = nil;
[visibleAnnotations release]; visibleAnnotations = nil;
self.quadTree = nil;
... ... @@ -322,25 +275,19 @@
_delegateHasBeforeMapMove = [delegate respondsToSelector:@selector(beforeMapMove:)];
_delegateHasAfterMapMove = [delegate respondsToSelector:@selector(afterMapMove:)];
_delegateHasAfterMapMoveDeceleration = [delegate respondsToSelector:@selector(afterMapMoveDeceleration:)];
_delegateHasBeforeMapZoomByFactor = [delegate respondsToSelector:@selector(beforeMapZoom:byFactor:near:)];
_delegateHasAfterMapZoomByFactor = [delegate respondsToSelector:@selector(afterMapZoom:byFactor:near:)];
_delegateHasBeforeMapZoom = [delegate respondsToSelector:@selector(beforeMapZoom:)];
_delegateHasAfterMapZoom = [delegate respondsToSelector:@selector(afterMapZoom:)];
_delegateHasMapViewRegionDidChange = [delegate respondsToSelector:@selector(mapViewRegionDidChange:)];
_delegateHasBeforeMapRotate = [delegate respondsToSelector:@selector(beforeMapRotate:fromAngle:)];
_delegateHasAfterMapRotate = [delegate respondsToSelector:@selector(afterMapRotate:toAngle:)];
_delegateHasDoubleTapOnMap = [delegate respondsToSelector:@selector(doubleTapOnMap:at:)];
_delegateHasDoubleTapTwoFingersOnMap = [delegate respondsToSelector:@selector(doubleTapTwoFingersOnMap:at:)];
_delegateHasSingleTapOnMap = [delegate respondsToSelector:@selector(singleTapOnMap:at:)];
_delegateHasLongSingleTapOnMap = [delegate respondsToSelector:@selector(longSingleTapOnMap:at:)];
_delegateHasTapOnMarker = [delegate respondsToSelector:@selector(tapOnAnnotation:onMap:)];
_delegateHasTapOnLabelForMarker = [delegate respondsToSelector:@selector(tapOnLabelForAnnotation:onMap:)];
_delegateHasAfterMapTouch = [delegate respondsToSelector:@selector(afterMapTouch:)];
_delegateHasTapOnAnnotation = [delegate respondsToSelector:@selector(tapOnAnnotation:onMap:)];
_delegateHasTapOnLabelForAnnotation = [delegate respondsToSelector:@selector(tapOnLabelForAnnotation:onMap:)];
_delegateHasShouldDragMarker = [delegate respondsToSelector:@selector(mapView:shouldDragAnnotation:withEvent:)];
_delegateHasDidDragMarker = [delegate respondsToSelector:@selector(mapView:didDragAnnotation:withEvent:)];
... ... @@ -356,7 +303,7 @@
}
#pragma mark -
#pragma mark Tile Source Bounds
#pragma mark Bounds
- (BOOL)projectedBounds:(RMProjectedRect)bounds containsPoint:(RMProjectedPoint)point
{
... ... @@ -371,46 +318,51 @@
return YES;
}
// What does this method do?
- (RMProjectedRect)projectedRectFromLatitudeLongitudeBounds:(RMSphericalTrapezium)bounds
{
CLLocationCoordinate2D ne = bounds.northEast;
CLLocationCoordinate2D sw = bounds.southWest;
float pixelBuffer = kZoomRectPixelBuffer;
CLLocationCoordinate2D southWest = bounds.southWest;
CLLocationCoordinate2D northEast = bounds.northEast;
CLLocationCoordinate2D midpoint = {
.latitude = (ne.latitude + sw.latitude) / 2,
.longitude = (ne.longitude + sw.longitude) / 2
.latitude = (northEast.latitude + southWest.latitude) / 2,
.longitude = (northEast.longitude + southWest.longitude) / 2
};
RMProjectedPoint myOrigin = [projection coordinateToProjectedPoint:midpoint];
RMProjectedPoint nePoint = [projection coordinateToProjectedPoint:ne];
RMProjectedPoint swPoint = [projection coordinateToProjectedPoint:sw];
RMProjectedPoint myPoint = {.x = nePoint.x - swPoint.x, .y = nePoint.y - swPoint.y};
RMProjectedPoint southWestPoint = [projection coordinateToProjectedPoint:southWest];
RMProjectedPoint northEastPoint = [projection coordinateToProjectedPoint:northEast];
RMProjectedPoint myPoint = {
.x = northEastPoint.x - southWestPoint.x,
.y = northEastPoint.y - southWestPoint.y
};
// Create the new zoom layout
RMProjectedRect zoomRect;
//Default is with scale = 2.0 mercators/pixel
zoomRect.size.width = [self screenBounds].size.width * 2.0;
zoomRect.size.height = [self screenBounds].size.height * 2.0;
if ((myPoint.x / [self screenBounds].size.width) < (myPoint.y / [self screenBounds].size.height))
// Default is with scale = 2.0 * mercators/pixel
zoomRect.size.width = [self bounds].size.width * 2.0;
zoomRect.size.height = [self bounds].size.height * 2.0;
if ((myPoint.x / [self bounds].size.width) < (myPoint.y / [self bounds].size.height))
{
if ((myPoint.y / ([self screenBounds].size.height - pixelBuffer)) > 1)
if ((myPoint.y / ([self bounds].size.height - pixelBuffer)) > 1)
{
zoomRect.size.width = [self screenBounds].size.width * (myPoint.y / ([self screenBounds].size.height - pixelBuffer));
zoomRect.size.height = [self screenBounds].size.height * (myPoint.y / ([self screenBounds].size.height - pixelBuffer));
zoomRect.size.width = [self bounds].size.width * (myPoint.y / ([self bounds].size.height - pixelBuffer));
zoomRect.size.height = [self bounds].size.height * (myPoint.y / ([self bounds].size.height - pixelBuffer));
}
}
else
{
if ((myPoint.x / ([self screenBounds].size.width - pixelBuffer)) > 1)
if ((myPoint.x / ([self bounds].size.width - pixelBuffer)) > 1)
{
zoomRect.size.width = [self screenBounds].size.width * (myPoint.x / ([self screenBounds].size.width - pixelBuffer));
zoomRect.size.height = [self screenBounds].size.height * (myPoint.x / ([self screenBounds].size.width - pixelBuffer));
zoomRect.size.width = [self bounds].size.width * (myPoint.x / ([self bounds].size.width - pixelBuffer));
zoomRect.size.height = [self bounds].size.height * (myPoint.x / ([self bounds].size.width - pixelBuffer));
}
}
myOrigin.x = myOrigin.x - (zoomRect.size.width / 2);
myOrigin.y = myOrigin.y - (zoomRect.size.height / 2);
RMLog(@"Origin is calculated at: %f, %f", [projection projectedPointToCoordinate:myOrigin].latitude, [projection projectedPointToCoordinate:myOrigin].longitude);
RMLog(@"Origin is calculated at: %f, %f", [projection projectedPointToCoordinate:myOrigin].longitude, [projection projectedPointToCoordinate:myOrigin].latitude);
zoomRect.origin = myOrigin;
... ... @@ -429,229 +381,220 @@
return [self projectedBounds:tileSourceProjectedBounds containsPoint:point];
}
- (BOOL)tileSourceBoundsContainScreenPoint:(CGPoint)point
- (BOOL)tileSourceBoundsContainScreenPoint:(CGPoint)pixelCoordinate
{
RMProjectedPoint projectedPoint = [mercatorToScreenProjection projectScreenPointToProjectedPoint:point];
RMProjectedPoint projectedPoint = [self pixelToProjectedPoint:pixelCoordinate];
return [self tileSourceBoundsContainProjectedPoint:projectedPoint];
}
#pragma mark -
#pragma mark Movement
- (void)moveToProjectedPoint:(RMProjectedPoint)aPoint
- (CLLocationCoordinate2D)mapCenterCoordinate
{
if (_delegateHasBeforeMapMove) [delegate beforeMapMove:self];
[self setMapCenterProjectedPoint:aPoint];
if (_delegateHasAfterMapMove) [delegate afterMapMove:self];
if (_delegateHasMapViewRegionDidChange) [delegate mapViewRegionDidChange:self];
return [projection projectedPointToCoordinate:[self mapCenterProjectedPoint]];
}
- (void)moveToCoordinate:(CLLocationCoordinate2D)coordinate
- (void)setMapCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
{
[self stopDeceleration];
if (_moveAnimationTimer != nil) {
[_moveAnimationTimer invalidate]; _moveAnimationTimer = nil;
} else {
if (_delegateHasBeforeMapMove) [delegate beforeMapMove:self];
}
RMProjectedPoint projectedPoint = [[self projection] coordinateToProjectedPoint:coordinate];
[self setMapCenterProjectedPoint:projectedPoint];
if (_delegateHasAfterMapMove) [delegate afterMapMove:self];
if (_delegateHasMapViewRegionDidChange) [delegate mapViewRegionDidChange:self];
[self setMapCenterProjectedPoint:[projection coordinateToProjectedPoint:centerCoordinate]];
}
- (void)moveBy:(CGSize)delta andCorrectAllAnnotations:(BOOL)correctAllSublayers
{
RMProjectedPoint projectedCenter = [mercatorToScreenProjection projectedCenter];
RMProjectedSize XYDelta = [mercatorToScreenProjection projectScreenSizeToProjectedSize:delta];
projectedCenter.x = projectedCenter.x - XYDelta.width;
projectedCenter.y = projectedCenter.y - XYDelta.height;
// ===
if (![self tileSourceBoundsContainProjectedPoint:projectedCenter])
return;
[mercatorToScreenProjection moveScreenBy:delta];
[imagesOnScreen moveBy:delta];
[tileLoader moveBy:delta];
[self correctPositionOfAllAnnotationsIncludingInvisibles:correctAllSublayers];
}
// from http://iphonedevelopment.blogspot.com/2010/12/more-animation-curves-than-you-can.html
double CubicEaseInOut(double t, double start, double end)
- (RMProjectedPoint)mapCenterProjectedPoint
{
if (t <= 0.0) return start;
else if (t >= 1.0) return end;
CGPoint center = CGPointMake(mapScrollView.contentOffset.x + mapScrollView.bounds.size.width/2.0, mapScrollView.contentSize.height - (mapScrollView.contentOffset.y + mapScrollView.bounds.size.height/2.0));
t *= 2.0;
if (t < 1.0) return ((end / 2.0) * t * t * t) + start - 1.0;
t -= 2.0;
return (end / 2.0) * (t * t * t + 2.0) + start - 1.0;
}
RMProjectedRect planetBounds = projection.planetBounds;
RMProjectedPoint normalizedProjectedPoint;
normalizedProjectedPoint.x = (center.x * self.metersPerPixel) - fabs(planetBounds.origin.x);
normalizedProjectedPoint.y = (center.y * self.metersPerPixel) - fabs(planetBounds.origin.y);
double CubicEaseOut(double t, double start, double end)
{
if (t <= 0.0) return start;
else if (t >= 1.0) return end;
// RMLog(@"centerProjectedPoint: {%f,%f}", normalizedProjectedPoint.x, normalizedProjectedPoint.y);
t--;
return end * (t * t * t + 1.0) + start - 1.0;
return normalizedProjectedPoint;
}
- (void)stopMoveAnimation
- (void)setMapCenterProjectedPoint:(RMProjectedPoint)centerProjectedPoint animated:(BOOL)animated
{
if (_moveAnimationTimer != nil) {
[_moveAnimationTimer invalidate]; _moveAnimationTimer = nil;
if (![self tileSourceBoundsContainProjectedPoint:centerProjectedPoint])
return;
if (_delegateHasAfterMapMove) [delegate afterMapMove:self];
if (_delegateHasAfterMapMoveDeceleration) [delegate afterMapMoveDeceleration:self];
if (_delegateHasMapViewRegionDidChange) [delegate mapViewRegionDidChange:self];
}
}
if (_delegateHasBeforeMapMove) [delegate beforeMapMove:self];
- (void)moveAnimationStep:(NSTimer *)timer
{
if (++_moveAnimationCurrentStep > _moveAnimationSteps) {
[self moveToProjectedPoint:_moveAnimationEndPoint];
[self stopMoveAnimation];
return;
}
// RMLog(@"Current contentSize: {%.0f,%.0f}, zoom: %f", mapScrollView.contentSize.width, mapScrollView.contentSize.height, self.zoom);
double t = _moveAnimationCurrentStep / _moveAnimationSteps;
RMProjectedRect planetBounds = projection.planetBounds;
RMProjectedPoint normalizedProjectedPoint;
normalizedProjectedPoint.x = centerProjectedPoint.x + fabs(planetBounds.origin.x);
normalizedProjectedPoint.y = centerProjectedPoint.y + fabs(planetBounds.origin.y);
RMProjectedPoint nextPoint = RMProjectedPointMake(_moveAnimationStartPoint.x + CubicEaseInOut(t, 0, _moveAnimationEndPoint.x - _moveAnimationStartPoint.x), _moveAnimationStartPoint.y + CubicEaseInOut(t, 0, _moveAnimationEndPoint.y - _moveAnimationStartPoint.y));
[mapScrollView setContentOffset:CGPointMake(normalizedProjectedPoint.x / self.metersPerPixel - mapScrollView.bounds.size.width/2.0,
mapScrollView.contentSize.height - ((normalizedProjectedPoint.y / self.metersPerPixel) + mapScrollView.bounds.size.height/2.0))
animated:animated];
if (fabs(nextPoint.x - _moveAnimationEndPoint.x) < 10.0 && fabs(nextPoint.y - _moveAnimationEndPoint.y) < 10.0) {
[self moveToProjectedPoint:_moveAnimationEndPoint];
[self stopMoveAnimation];
return;
}
// RMLog(@"setMapCenterProjectedPoint: {%f,%f} -> {%.0f,%.0f}", centerProjectedPoint.x, centerProjectedPoint.y, mapScrollView.contentOffset.x, mapScrollView.contentOffset.y);
// RMLog(@"Time t=%.2f: (%f,%f) < (%f,%f) < (%f,%f)", t, _moveAnimationStartPoint.easting, _moveAnimationStartPoint.northing, nextPoint.easting, nextPoint.northing, _moveAnimationEndPoint.easting, _moveAnimationEndPoint.northing);
if (_delegateHasAfterMapMove) [delegate afterMapMove:self];
if (_delegateHasMapViewRegionDidChange) [delegate mapViewRegionDidChange:self];
[mercatorToScreenProjection setProjectedCenter:nextPoint];
[tileLoader reload];
[self correctPositionOfAllAnnotationsIncludingInvisibles:NO];
[self correctPositionOfAllAnnotations];
}
- (void)moveToCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated
- (void)setMapCenterProjectedPoint:(RMProjectedPoint)centerProjectedPoint
{
[self stopDeceleration];
[self stopMoveAnimation];
if (!animated) {
[self moveToCoordinate:coordinate];
return;
}
[self setMapCenterProjectedPoint:centerProjectedPoint animated:NO];
}
_moveAnimationStartPoint = [mercatorToScreenProjection projectedCenter];
_moveAnimationEndPoint = [[self projection] coordinateToProjectedPoint:coordinate];
// ===
if (![self tileSourceBoundsContainProjectedPoint:_moveAnimationEndPoint])
return;
- (void)moveToProjectedPoint:(RMProjectedPoint)aPoint animated:(BOOL)animated
{
[self setMapCenterProjectedPoint:aPoint animated:animated];
}
_moveAnimationSteps = roundf(kMoveAnimationDuration / kMoveAnimationStepDuration);
_moveAnimationCurrentStep = 0.0;
- (void)moveToProjectedPoint:(RMProjectedPoint)aPoint
{
[self moveToProjectedPoint:aPoint animated:NO];
}
if (_delegateHasBeforeMapMove) [delegate beforeMapMove:self];
- (void)moveToCoordinate:(CLLocationCoordinate2D)coordinate
{
[self moveToProjectedPoint:[projection coordinateToProjectedPoint:coordinate]];
}
_moveAnimationTimer = [NSTimer scheduledTimerWithTimeInterval:kMoveAnimationStepDuration
target:self
selector:@selector(moveAnimationStep:)
userInfo:nil
repeats:YES];
- (void)moveToCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated
{
[self moveToProjectedPoint:[projection coordinateToProjectedPoint:coordinate] animated:animated];
}
- (void)moveBy:(CGSize)delta isAnimationStep:(BOOL)isAnimationStep
// ===
- (void)moveBy:(CGSize)delta
{
if (_constrainMovement)
{
RMMercatorToScreenProjection *mtsp = self.mercatorToScreenProjection;
// calculate new bounds after move
RMProjectedRect pBounds = [mtsp projectedBounds];
RMProjectedSize XYDelta = [mtsp projectScreenSizeToProjectedSize:delta];
CGSize sizeRatio = CGSizeMake(((delta.width == 0) ? 0 : XYDelta.width / delta.width),
((delta.height == 0) ? 0 : XYDelta.height / delta.height));
RMProjectedRect newBounds = pBounds;
// move the rect by delta
newBounds.origin.x -= XYDelta.width;
newBounds.origin.y -= XYDelta.height;
// see if new bounds are within constrained bounds, and constrain if necessary
BOOL constrained = NO;
if (newBounds.origin.y < _southWestConstraint.y) {
newBounds.origin.y = _southWestConstraint.y;
constrained = YES;
}
if (newBounds.origin.y + newBounds.size.height > _northEastConstraint.y) {
newBounds.origin.y = _northEastConstraint.y - newBounds.size.height;
constrained = YES;
}
if (newBounds.origin.x < _southWestConstraint.x) {
newBounds.origin.x = _southWestConstraint.x;
constrained = YES;
}
if (newBounds.origin.x + newBounds.size.width > _northEastConstraint.x) {
newBounds.origin.x = _northEastConstraint.x - newBounds.size.width;
constrained = YES;
}
if (constrained)
{
// Adjust delta to match constraint
XYDelta.height = pBounds.origin.y - newBounds.origin.y;
XYDelta.width = pBounds.origin.x - newBounds.origin.x;
delta = CGSizeMake(((sizeRatio.width == 0) ? 0 : XYDelta.width / sizeRatio.width),
((sizeRatio.height == 0) ? 0 : XYDelta.height / sizeRatio.height));
}
// RMMercatorToScreenProjection *mtsp = self.mercatorToScreenProjection;
//
// // calculate new bounds after move
// RMProjectedRect pBounds = [mtsp projectedBounds];
// RMProjectedSize XYDelta = [mtsp projectScreenSizeToProjectedSize:delta];
// CGSize sizeRatio = CGSizeMake(((delta.width == 0) ? 0 : XYDelta.width / delta.width),
// ((delta.height == 0) ? 0 : XYDelta.height / delta.height));
// RMProjectedRect newBounds = pBounds;
//
// // move the rect by delta
// newBounds.origin.x -= XYDelta.width;
// newBounds.origin.y -= XYDelta.height;
//
// // see if new bounds are within constrained bounds, and constrain if necessary
// BOOL constrained = NO;
// if (newBounds.origin.y < _southWestConstraint.y) {
// newBounds.origin.y = _southWestConstraint.y;
// constrained = YES;
// }
// if (newBounds.origin.y + newBounds.size.height > _northEastConstraint.y) {
// newBounds.origin.y = _northEastConstraint.y - newBounds.size.height;
// constrained = YES;
// }
// if (newBounds.origin.x < _southWestConstraint.x) {
// newBounds.origin.x = _southWestConstraint.x;
// constrained = YES;
// }
// if (newBounds.origin.x + newBounds.size.width > _northEastConstraint.x) {
// newBounds.origin.x = _northEastConstraint.x - newBounds.size.width;
// constrained = YES;
// }
//
// if (constrained)
// {
// // Adjust delta to match constraint
// XYDelta.height = pBounds.origin.y - newBounds.origin.y;
// XYDelta.width = pBounds.origin.x - newBounds.origin.x;
// delta = CGSizeMake(((sizeRatio.width == 0) ? 0 : XYDelta.width / sizeRatio.width),
// ((sizeRatio.height == 0) ? 0 : XYDelta.height / sizeRatio.height));
// }
}
if (_delegateHasBeforeMapMove) [delegate beforeMapMove:self];
[self moveBy:delta andCorrectAllAnnotations:!isAnimationStep];
CGPoint contentOffset = mapScrollView.contentOffset;
contentOffset.x += delta.width;
contentOffset.y += delta.height;
mapScrollView.contentOffset = contentOffset;
if (_delegateHasAfterMapMove) [delegate afterMapMove:self];
}
- (void)moveBy:(CGSize)delta
{
[self moveBy:delta isAnimationStep:NO];
}
// ===
- (void)setConstraintsSouthWest:(CLLocationCoordinate2D)sw northEeast:(CLLocationCoordinate2D)ne
- (void)setConstraintsSouthWest:(CLLocationCoordinate2D)southWest northEeast:(CLLocationCoordinate2D)northEast
{
RMProjection *proj = self.projection;
RMProjectedPoint projectedSouthWest = [projection coordinateToProjectedPoint:southWest];
RMProjectedPoint projectedNorthEast = [projection coordinateToProjectedPoint:northEast];
RMProjectedPoint projectedNE = [proj coordinateToProjectedPoint:ne];
RMProjectedPoint projectedSW = [proj coordinateToProjectedPoint:sw];
[self setProjectedConstraintsSouthWest:projectedSW northEast:projectedNE];
[self setProjectedConstraintsSouthWest:projectedSouthWest northEast:projectedNorthEast];
}
- (void)setProjectedConstraintsSouthWest:(RMProjectedPoint)sw northEast:(RMProjectedPoint)ne
- (void)setProjectedConstraintsSouthWest:(RMProjectedPoint)southWest northEast:(RMProjectedPoint)northEast
{
_southWestConstraint = sw;
_northEastConstraint = ne;
_southWestConstraint = southWest;
_northEastConstraint = northEast;
_constrainMovement = YES;
}
#pragma mark -
#pragma mark Zoom
- (RMProjectedRect)projectedBounds
{
CGPoint bottomLeft = CGPointMake(mapScrollView.contentOffset.x, mapScrollView.contentSize.height - (mapScrollView.contentOffset.y + mapScrollView.bounds.size.height));
RMProjectedRect planetBounds = projection.planetBounds;
RMProjectedRect normalizedProjectedRect;
normalizedProjectedRect.origin.x = (bottomLeft.x * self.metersPerPixel) - fabs(planetBounds.origin.x);
normalizedProjectedRect.origin.y = (bottomLeft.y * self.metersPerPixel) - fabs(planetBounds.origin.y);
normalizedProjectedRect.size.width = mapScrollView.bounds.size.width * self.metersPerPixel;
normalizedProjectedRect.size.height = mapScrollView.bounds.size.height * self.metersPerPixel;
return normalizedProjectedRect;
}
- (void)setProjectedBounds:(RMProjectedRect)boundsRect
{
[self setProjectedBounds:boundsRect animated:YES];
}
- (void)setProjectedBounds:(RMProjectedRect)boundsRect animated:(BOOL)animated
{
RMProjectedRect planetBounds = projection.planetBounds;
RMProjectedPoint normalizedProjectedPoint;
normalizedProjectedPoint.x = boundsRect.origin.x + fabs(planetBounds.origin.x);
normalizedProjectedPoint.y = boundsRect.origin.y + fabs(planetBounds.origin.y);
float zoomScale = mapScrollView.zoomScale;
CGRect zoomRect = CGRectMake((normalizedProjectedPoint.x / self.metersPerPixel) / zoomScale,
((planetBounds.size.height - normalizedProjectedPoint.y - boundsRect.size.height) / self.metersPerPixel) / zoomScale,
(boundsRect.size.width / self.metersPerPixel) / zoomScale,
(boundsRect.size.height / self.metersPerPixel) / zoomScale);
[mapScrollView zoomToRect:zoomRect animated:animated];
[self correctPositionOfAllAnnotations];
}
- (float)adjustedZoomForCurrentBoundingMask:(float)zoomFactor
{
if (boundingMask == RMMapNoMinBound)
return zoomFactor;
double newMPP = self.metersPerPixel / zoomFactor;
double newMetersPerPixel = self.metersPerPixel / zoomFactor;
RMProjectedRect mercatorBounds = [[tileSource projection] planetBounds];
RMProjectedRect mercatorBounds = [projection planetBounds];
// Check for MinWidthBound
if (boundingMask & RMMapMinWidthBound)
{
double newMapContentsWidth = mercatorBounds.size.width / newMPP;
double screenBoundsWidth = [self screenBounds].size.width;
double newMapContentsWidth = mercatorBounds.size.width / newMetersPerPixel;
double screenBoundsWidth = [self bounds].size.width;
double mapContentWidth;
if (newMapContentsWidth < screenBoundsWidth)
... ... @@ -665,8 +608,8 @@ double CubicEaseOut(double t, double start, double end)
// Check for MinHeightBound
if (boundingMask & RMMapMinHeightBound)
{
double newMapContentsHeight = mercatorBounds.size.height / newMPP;
double screenBoundsHeight = [self screenBounds].size.height;
double newMapContentsHeight = mercatorBounds.size.height / newMetersPerPixel;
double screenBoundsHeight = [self bounds].size.height;
double mapContentHeight;
if (newMapContentsHeight < screenBoundsHeight)
... ... @@ -697,27 +640,7 @@ double CubicEaseOut(double t, double start, double end)
}
}
// \bug this is a no-op, not a clamp, if new zoom would be outside of minzoom/maxzoom range
- (void)zoomContentByFactor:(float)zoomFactor near:(CGPoint)pivot
{
if (![self tileSourceBoundsContainScreenPoint:pivot])
return;
zoomFactor = [self adjustedZoomForCurrentBoundingMask:zoomFactor];
// pre-calculate zoom so we can tell if we want to perform it
float newZoom = [mercatorToTileProjection calculateZoomFromScale:(self.metersPerPixel / zoomFactor)];
if ((newZoom > minZoom) && (newZoom < maxZoom))
{
[mercatorToScreenProjection zoomScreenByFactor:zoomFactor near:pivot];
[imagesOnScreen zoomByFactor:zoomFactor near:pivot];
[tileLoader zoomByFactor:zoomFactor near:pivot];
[self correctPositionOfAllAnnotations];
}
}
- (void)zoomContentByFactor:(float)zoomFactor near:(CGPoint)pivot animated:(BOOL)animated withCallback:(RMMapView *)callback isAnimationStep:(BOOL)isAnimationStep
- (void)zoomContentByFactor:(float)zoomFactor near:(CGPoint)pivot animated:(BOOL)animated
{
if (![self tileSourceBoundsContainScreenPoint:pivot])
return;
... ... @@ -745,32 +668,14 @@ double CubicEaseOut(double t, double start, double end)
if ([self shouldZoomToTargetZoom:targetZoom withZoomFactor:zoomFactor])
{
if (animated)
{
// goal is to complete the animation in animTime seconds
double nSteps = round(kZoomAnimationAnimationTime / kZoomAnimationStepTime);
double zoomIncr = zoomDelta / nSteps;
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithDouble:zoomIncr], @"zoomIncr",
[NSNumber numberWithDouble:targetZoom], @"targetZoom",
[NSValue valueWithCGPoint:pivot], @"pivot",
[NSNumber numberWithFloat:zoomFactor], @"factor",
callback, @"callback",
nil];
[NSTimer scheduledTimerWithTimeInterval:kZoomAnimationStepTime
target:self
selector:@selector(animatedZoomStep:)
userInfo:userInfo
repeats:YES];
}
else
{
[mercatorToScreenProjection zoomScreenByFactor:zoomFactor near:pivot];
[imagesOnScreen zoomByFactor:zoomFactor near:pivot];
[tileLoader zoomByFactor:zoomFactor near:pivot];
[self correctPositionOfAllAnnotationsIncludingInvisibles:!isAnimationStep];
}
float zoomScale = mapScrollView.zoomScale;
CGSize newZoomSize = CGSizeMake(mapScrollView.bounds.size.width / zoomFactor,
mapScrollView.bounds.size.height / zoomFactor);
CGRect zoomRect = CGRectMake(((mapScrollView.contentOffset.x + pivot.x) - (newZoomSize.width / 2.0)) / zoomScale,
((mapScrollView.contentOffset.y + pivot.y) - (newZoomSize.height / 2.0)) / zoomScale,
newZoomSize.width / zoomScale,
newZoomSize.height / zoomScale);
[mapScrollView zoomToRect:zoomRect animated:animated];
}
else
{
... ... @@ -781,50 +686,6 @@ double CubicEaseOut(double t, double start, double end)
}
}
- (void)zoomContentByFactor:(float)zoomFactor near:(CGPoint)pivot animated:(BOOL)animated
{
[self zoomContentByFactor:zoomFactor near:pivot animated:animated withCallback:nil isAnimationStep:NO];
}
- (void)animationStepped
{
if (_delegateHasMapViewRegionDidChange) [delegate mapViewRegionDidChange:self];
}
- (void)animationFinishedWithZoomFactor:(float)zoomFactor near:(CGPoint)p
{
if (_delegateHasAfterMapZoomByFactor)
[delegate afterMapZoom:self byFactor:zoomFactor near:p];
}
- (void)animatedZoomStep:(NSTimer *)timer
{
double zoomIncr = [[[timer userInfo] objectForKey:@"zoomIncr"] doubleValue];
double targetZoom = [[[timer userInfo] objectForKey:@"targetZoom"] doubleValue];
NSDictionary *userInfo = [[[timer userInfo] retain] autorelease];
RMMapView *callback = [userInfo objectForKey:@"callback"];
if ((zoomIncr > 0 && [self zoom] >= targetZoom-1.0e-6) || (zoomIncr < 0 && [self zoom] <= targetZoom+1.0e-6))
{
if ([self zoom] != targetZoom)
[self setZoom:targetZoom];
[timer invalidate]; // ASAP
if ([callback respondsToSelector:@selector(animationFinishedWithZoomFactor:near:)])
[callback animationFinishedWithZoomFactor:[[userInfo objectForKey:@"factor"] floatValue] near:[[userInfo objectForKey:@"pivot"] CGPointValue]];
[self correctPositionOfAllAnnotationsIncludingInvisibles:YES];
}
else
{
float zoomFactorStep = exp2f(zoomIncr);
[self zoomContentByFactor:zoomFactorStep near:[[[timer userInfo] objectForKey:@"pivot"] CGPointValue] animated:NO withCallback:nil isAnimationStep:YES];
if ([callback respondsToSelector:@selector(animationStepped)])
[callback animationStepped];
}
}
- (float)nextNativeZoomFactor
{
float newZoom = fminf(floorf([self zoom] + 1.0), [self maxZoom]);
... ... @@ -846,9 +707,10 @@ double CubicEaseOut(double t, double start, double end)
{
// Calculate rounded zoom
float newZoom = fmin(floorf([self zoom] + 1.0), [self maxZoom]);
RMLog(@"[self minZoom] %f [self zoom] %f [self maxZoom] %f newzoom %f", [self minZoom], [self zoom], [self maxZoom], newZoom);
if (newZoom == self.zoom) return;
float factor = exp2f(newZoom - [self zoom]);
RMLog(@"zoom by factor: %f around {%f,%f}", factor, pivot.x, pivot.y);
[self zoomContentByFactor:factor near:pivot animated:animated];
}
... ... @@ -856,9 +718,10 @@ double CubicEaseOut(double t, double start, double end)
{
// Calculate rounded zoom
float newZoom = fmax(ceilf([self zoom] - 1.0), [self minZoom]);
RMLog(@"[self minZoom] %f [self zoom] %f [self maxZoom] %f newzoom %f", [self minZoom], [self zoom], [self maxZoom], newZoom);
if (newZoom == self.zoom) return;
float factor = exp2f(newZoom - [self zoom]);
RMLog(@"zoom by factor: %f around {%f,%f}", factor, pivot.x, pivot.y);
[self zoomContentByFactor:factor near:pivot animated:animated];
}
... ... @@ -869,6 +732,7 @@ double CubicEaseOut(double t, double start, double end)
- (void)zoomByFactor:(float)zoomFactor near:(CGPoint)center animated:(BOOL)animated
{
// \Bug: Move this to zoomContentByFactor, or merge zoomContentByFactor with this method
if (_constrainMovement)
{
// check that bounds after zoom don't exceed map constraints
... ... @@ -901,37 +765,37 @@ double CubicEaseOut(double t, double start, double end)
//zooming out zoomFactor < 1
if ((zoomGreaterMin && zoomLessMax) || (zoomAtMax && zoomFactor<1) || (zoomAtMin && zoomFactor>1))
{
// if I'm here it means I could zoom, now we have to see what will happen after zoom
RMMercatorToScreenProjection *mtsp = self.mercatorToScreenProjection;
// get copies of mercatorRoScreenProjection's data
RMProjectedPoint origin = [mtsp origin];
float metersPerPixel = mtsp.metersPerPixel;
CGRect screenBounds = [mtsp screenBounds];
// this is copied from [RMMercatorToScreenBounds zoomScreenByFactor]
// First we move the origin to the pivot...
origin.x += center.x * metersPerPixel;
origin.y += (screenBounds.size.height - center.y) * metersPerPixel;
// Then scale by 1/factor
metersPerPixel /= _zoomFactor;
// Then translate back
origin.x -= center.x * metersPerPixel;
origin.y -= (screenBounds.size.height - center.y) * metersPerPixel;
origin = [mtsp.projection wrapPointHorizontally:origin];
// calculate new bounds
RMProjectedRect zRect;
zRect.origin = origin;
zRect.size.width = screenBounds.size.width * metersPerPixel;
zRect.size.height = screenBounds.size.height * metersPerPixel;
// can zoom only if within bounds
canZoom = !(zRect.origin.y < _southWestConstraint.y || zRect.origin.y+zRect.size.height > _northEastConstraint.y ||
zRect.origin.x < _southWestConstraint.x || zRect.origin.x+zRect.size.width > _northEastConstraint.x);
// // if I'm here it means I could zoom, now we have to see what will happen after zoom
// RMMercatorToScreenProjection *mtsp = self.mercatorToScreenProjection;
//
// // get copies of mercatorRoScreenProjection's data
// RMProjectedPoint origin = [mtsp origin];
// float metersPerPixel = mtsp.metersPerPixel;
// CGRect screenBounds = [mtsp screenBounds];
//
// // this is copied from [RMMercatorToScreenBounds zoomScreenByFactor]
// // First we move the origin to the pivot...
// origin.x += center.x * metersPerPixel;
// origin.y += (screenBounds.size.height - center.y) * metersPerPixel;
//
// // Then scale by 1/factor
// metersPerPixel /= _zoomFactor;
//
// // Then translate back
// origin.x -= center.x * metersPerPixel;
// origin.y -= (screenBounds.size.height - center.y) * metersPerPixel;
//
// origin = [mtsp.projection wrapPointHorizontally:origin];
//
// // calculate new bounds
// RMProjectedRect zRect;
// zRect.origin = origin;
// zRect.size.width = screenBounds.size.width * metersPerPixel;
// zRect.size.height = screenBounds.size.height * metersPerPixel;
//
// // can zoom only if within bounds
// canZoom = !(zRect.origin.y < _southWestConstraint.y || zRect.origin.y+zRect.size.height > _northEastConstraint.y ||
// zRect.origin.x < _southWestConstraint.x || zRect.origin.x+zRect.size.width > _northEastConstraint.x);
}
if (!canZoom) {
... ... @@ -940,13 +804,7 @@ double CubicEaseOut(double t, double start, double end)
}
}
if (_delegateHasBeforeMapZoomByFactor) [delegate beforeMapZoom:self byFactor:zoomFactor near:center];
[self zoomContentByFactor:zoomFactor near:center animated:animated withCallback:((animated && (_delegateHasAfterMapZoomByFactor || _delegateHasMapViewRegionDidChange)) ? self : nil) isAnimationStep:!animated];
if (!animated) {
if (_delegateHasAfterMapZoomByFactor) [delegate afterMapZoom:self byFactor:zoomFactor near:center];
if (_delegateHasMapViewRegionDidChange) [delegate mapViewRegionDidChange:self];
}
[self zoomContentByFactor:zoomFactor near:center animated:animated];
}
- (void)zoomByFactor:(float)zoomFactor near:(CGPoint)center
... ... @@ -957,75 +815,72 @@ double CubicEaseOut(double t, double start, double end)
#pragma mark -
#pragma mark Zoom With Bounds
- (void)zoomWithLatitudeLongitudeBoundsSouthWest:(CLLocationCoordinate2D)sw northEast:(CLLocationCoordinate2D)ne
- (void)zoomWithLatitudeLongitudeBoundsSouthWest:(CLLocationCoordinate2D)southWest northEast:(CLLocationCoordinate2D)northEast animated:(BOOL)animated
{
if (ne.latitude == sw.latitude && ne.longitude == sw.longitude) //There are no bounds, probably only one marker.
if (northEast.latitude == southWest.latitude && northEast.longitude == southWest.longitude) // There are no bounds, probably only one marker.
{
RMProjectedRect zoomRect;
RMProjectedPoint myOrigin = [projection coordinateToProjectedPoint:sw];
// Default is with scale = 2.0 mercators/pixel
zoomRect.size.width = [self screenBounds].size.width * 2.0;
zoomRect.size.height = [self screenBounds].size.height * 2.0;
myOrigin.x = myOrigin.x - (zoomRect.size.width / 2);
myOrigin.y = myOrigin.y - (zoomRect.size.height / 2);
RMProjectedPoint myOrigin = [projection coordinateToProjectedPoint:southWest];
// Default is with scale = 2.0 * mercators/pixel
zoomRect.size.width = [self bounds].size.width * 2.0;
zoomRect.size.height = [self bounds].size.height * 2.0;
myOrigin.x = myOrigin.x - (zoomRect.size.width / 2.0);
myOrigin.y = myOrigin.y - (zoomRect.size.height / 2.0);
zoomRect.origin = myOrigin;
[self zoomWithProjectedBounds:zoomRect];
[self setProjectedBounds:zoomRect animated:animated];
}
else
{
// Convert ne/sw into RMMercatorRect and call zoomWithBounds
// Convert northEast/southWest into RMMercatorRect and call zoomWithBounds
float pixelBuffer = kZoomRectPixelBuffer;
CLLocationCoordinate2D midpoint = {
.latitude = (ne.latitude + sw.latitude) / 2,
.longitude = (ne.longitude + sw.longitude) / 2
.latitude = (northEast.latitude + southWest.latitude) / 2,
.longitude = (northEast.longitude + southWest.longitude) / 2
};
RMProjectedPoint myOrigin = [projection coordinateToProjectedPoint:midpoint];
RMProjectedPoint nePoint = [projection coordinateToProjectedPoint:ne];
RMProjectedPoint swPoint = [projection coordinateToProjectedPoint:sw];
RMProjectedPoint southWestPoint = [projection coordinateToProjectedPoint:southWest];
RMProjectedPoint northEastPoint = [projection coordinateToProjectedPoint:northEast];
RMProjectedPoint myPoint = {
.x = nePoint.x - swPoint.x,
.y = nePoint.y - swPoint.y
.x = northEastPoint.x - southWestPoint.x,
.y = northEastPoint.y - southWestPoint.y
};
// Create the new zoom layout
RMProjectedRect zoomRect;
// Default is with scale = 2.0 mercators/pixel
zoomRect.size.width = [self screenBounds].size.width * 2.0;
zoomRect.size.height = [self screenBounds].size.height * 2.0;
if ((myPoint.x / [self screenBounds].size.width) < (myPoint.y / [self screenBounds].size.height))
// Default is with scale = 2.0 * mercators/pixel
zoomRect.size.width = self.bounds.size.width * 2.0;
zoomRect.size.height = self.bounds.size.height * 2.0;
if ((myPoint.x / self.bounds.size.width) < (myPoint.y / self.bounds.size.height))
{
if ((myPoint.y / ([self screenBounds].size.height - pixelBuffer)) > 1)
if ((myPoint.y / (self.bounds.size.height - pixelBuffer)) > 1)
{
zoomRect.size.width = [self screenBounds].size.width * (myPoint.y / ([self screenBounds].size.height - pixelBuffer));
zoomRect.size.height = [self screenBounds].size.height * (myPoint.y / ([self screenBounds].size.height - pixelBuffer));
zoomRect.size.width = self.bounds.size.width * (myPoint.y / (self.bounds.size.height - pixelBuffer));
zoomRect.size.height = self.bounds.size.height * (myPoint.y / (self.bounds.size.height - pixelBuffer));
}
}
else
{
if ((myPoint.x / ([self screenBounds].size.width - pixelBuffer)) > 1)
if ((myPoint.x / (self.bounds.size.width - pixelBuffer)) > 1)
{
zoomRect.size.width = [self screenBounds].size.width * (myPoint.x / ([self screenBounds].size.width - pixelBuffer));
zoomRect.size.height = [self screenBounds].size.height * (myPoint.x / ([self screenBounds].size.width - pixelBuffer));
zoomRect.size.width = self.bounds.size.width * (myPoint.x / (self.bounds.size.width - pixelBuffer));
zoomRect.size.height = self.bounds.size.height * (myPoint.x / (self.bounds.size.width - pixelBuffer));
}
}
myOrigin.x = myOrigin.x - (zoomRect.size.width / 2);
myOrigin.y = myOrigin.y - (zoomRect.size.height / 2);
RMLog(@"Origin is calculated at: %f, %f", [projection projectedPointToCoordinate:myOrigin].latitude, [projection projectedPointToCoordinate:myOrigin].longitude);
zoomRect.origin = myOrigin;
[self zoomWithProjectedBounds:zoomRect];
}
[self moveBy:CGSizeZero andCorrectAllAnnotations:YES];
RMProjectedPoint topRight = RMProjectedPointMake(myOrigin.x + zoomRect.size.width, myOrigin.y + zoomRect.size.height);
RMLog(@"zoomWithBoundingBox: {%f,%f} - {%f,%f}", [projection projectedPointToCoordinate:myOrigin].longitude, [projection projectedPointToCoordinate:myOrigin].latitude, [projection projectedPointToCoordinate:topRight].longitude, [projection projectedPointToCoordinate:topRight].latitude);
[self setProjectedBounds:zoomRect animated:animated];
}
}
- (void)zoomWithProjectedBounds:(RMProjectedRect)bounds
- (void)zoomWithLatitudeLongitudeBoundsSouthWest:(CLLocationCoordinate2D)southWest northEast:(CLLocationCoordinate2D)northEast
{
[self setProjectedBounds:bounds];
[tileLoader clearLoadedBounds];
[tileLoader updateLoadedImages];
[self correctPositionOfAllAnnotations];
[self zoomWithLatitudeLongitudeBoundsSouthWest:southWest northEast:northEast animated:YES];
}
#pragma mark -
... ... @@ -1037,231 +892,237 @@ double CubicEaseOut(double t, double start, double end)
}
#pragma mark -
#pragma mark Properties
- (void)setTileSource:(id <RMTileSource>)newTileSource
{
if (tileSource == newTileSource)
return;
minZoom = newTileSource.minZoom;
maxZoom = newTileSource.maxZoom + 1;
[self setZoom:[self zoom]]; // setZoom clamps zoom level to min/max limits
[tileSource autorelease];
tileSource = [newTileSource retain];
if (([tileSource minZoom] - minZoom) <= 1.0) {
RMLog(@"Graphics & memory are overly taxed if [contents minZoom] is more than 1.5 smaller than [tileSource minZoom]");
}
[projection release];
projection = [[tileSource projection] retain];
[mercatorToTileProjection release];
mercatorToTileProjection = [[tileSource mercatorToTileProjection] retain];
tileSourceProjectedBounds = (RMProjectedRect)[self projectedRectFromLatitudeLongitudeBounds:[tileSource latitudeLongitudeBoundingBox]];
[imagesOnScreen setTileCache:tileCache];
[imagesOnScreen setTileSource:tileSource];
[imagesOnScreen setCurrentCacheKey:[newTileSource uniqueTilecacheKey]];
#pragma mark MapView (ScrollView)
- (void)recreateMapView
{
[overlayView removeFromSuperview]; [overlayView release]; overlayView = nil;
[tiledLayerView release]; tiledLayerView = nil;
[mapScrollView removeFromSuperview]; [mapScrollView release]; mapScrollView = nil;
int tileSideLength = [[self tileSource] tileSideLength];
CGSize contentSize = CGSizeMake(2.0*tileSideLength, 2.0*tileSideLength); // zoom level 1
mapScrollView = [[UIScrollView alloc] initWithFrame:[self bounds]];
mapScrollView.delegate = self;
mapScrollView.backgroundColor = [UIColor clearColor];
mapScrollView.showsVerticalScrollIndicator = NO;
mapScrollView.showsHorizontalScrollIndicator = NO;
mapScrollView.scrollsToTop = NO;
// mapView.delaysContentTouches = NO;
mapScrollView.contentSize = contentSize;
mapScrollView.minimumZoomScale = [self minZoom];
mapScrollView.maximumZoomScale = 1 << (int)[self maxZoom];
mapScrollView.zoomScale = [self zoom];
mapScrollView.contentOffset = CGPointMake(0.0, 0.0);
RMProjectedRect planetBounds = projection.planetBounds;
metersPerPixel = planetBounds.size.width / mapScrollView.contentSize.width;
[mapScrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:NULL];
tiledLayerView = [[RMMapTiledLayerView alloc] initWithFrame:CGRectMake(0.0, 0.0, contentSize.width, contentSize.height) mapView:self];
tiledLayerView.delegate = self;
((CATiledLayer *)tiledLayerView.layer).tileSize = CGSizeMake(tileSideLength, tileSideLength);
[mapScrollView addSubview:tiledLayerView];
NSLog(@"Initialization contentSize: {%.0f,%.0f}, zoom: %f", mapScrollView.contentSize.width, mapScrollView.contentSize.height, self.zoom);
if (backgroundView)
[self insertSubview:mapScrollView aboveSubview:backgroundView];
else
[self insertSubview:mapScrollView atIndex:0];
[tileLoader reset];
[tileLoader reload];
overlayView = [[RMMapOverlayView alloc] initWithFrame:[self bounds]];
overlayView.delegate = self;
[self insertSubview:overlayView aboveSubview:mapScrollView];
}
- (id <RMTileSource>)tileSource
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return [[tileSource retain] autorelease];
return tiledLayerView;
}
- (void)setRenderer:(RMCoreAnimationRenderer *)newRenderer
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
if (renderer == newRenderer)
return;
[imagesOnScreen setDelegate:newRenderer];
[[renderer layer] removeFromSuperlayer];
[renderer release];
renderer = [newRenderer retain];
if (renderer == nil)
return;
// CGRect rect = [self screenBounds];
// RMLog(@"%f %f %f %f", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
[[renderer layer] setFrame:[self screenBounds]];
if (background != nil)
[self.layer insertSublayer:[renderer layer] above:background];
else if (overlay != nil)
[self.layer insertSublayer:[renderer layer] below:overlay];
else
[self.layer insertSublayer:[renderer layer] atIndex: 0];
if (_delegateHasBeforeMapMove)
[delegate beforeMapMove:self];
}
- (RMCoreAnimationRenderer *)renderer
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
return [[renderer retain] autorelease];
if (!decelerate && _delegateHasAfterMapMove)
[delegate afterMapMove:self];
}
- (void)setBackground:(CALayer *)aLayer
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
if (background == aLayer)
return;
if (background != nil) {
[background release];
[background removeFromSuperlayer];
}
background = [aLayer retain];
if (background == nil)
return;
background.frame = [self screenBounds];
if ([renderer layer] != nil)
[self.layer insertSublayer:background below:[renderer layer]];
else if (overlay != nil)
[self.layer insertSublayer:background below:overlay];
else
[self.layer insertSublayer:[renderer layer] atIndex:0];
if (_delegateHasAfterMapMove)
[delegate afterMapMove:self];
}
- (CALayer *)background
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
{
return [[background retain] autorelease];
if (_delegateHasBeforeMapZoom)
[delegate beforeMapZoom:self];
}
- (void)setOverlay:(RMMapLayer *)aLayer
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
if (overlay == aLayer)
return;
if (_delegateHasAfterMapZoom)
[delegate afterMapZoom:self];
}
if (overlay != nil) {
[overlay release];
[overlay removeFromSuperlayer];
}
// Overlay
overlay = [aLayer retain];
if (overlay == nil)
return;
overlay.frame = [self screenBounds];
overlay.masksToBounds = YES;
- (void)mapOverlayView:(RMMapOverlayView *)aMapOverlayView tapOnAnnotation:(RMAnnotation *)anAnnotation
{
if (_delegateHasTapOnAnnotation)
[delegate tapOnAnnotation:anAnnotation onMap:self];
}
if ([renderer layer] != nil)
[self.layer insertSublayer:overlay above:[renderer layer]];
else if (background != nil)
[self.layer insertSublayer:overlay above:background];
else
[self.layer insertSublayer:[renderer layer] atIndex:0];
- (void)mapOverlayView:(RMMapOverlayView *)aMapOverlayView tapOnLabelForAnnotation:(RMAnnotation *)anAnnotation
{
if (_delegateHasTapOnLabelForAnnotation)
[delegate tapOnLabelForAnnotation:anAnnotation onMap:self];
}
- (RMMapLayer *)overlay
// Tiled layer
- (void)mapTiledLayerView:(RMMapTiledLayerView *)aTiledLayerView singleTapAtPoint:(CGPoint)aPoint
{
return [[overlay retain] autorelease];
if (_delegateHasSingleTapOnMap)
[delegate singleTapOnMap:self at:aPoint];
}
- (CLLocationCoordinate2D)mapCenterCoordinate
- (void)mapTiledLayerView:(RMMapTiledLayerView *)aTiledLayerView doubleTapAtPoint:(CGPoint)aPoint
{
return [projection projectedPointToCoordinate:[mercatorToScreenProjection projectedCenter]];
[self zoomInToNextNativeZoomAt:aPoint animated:YES];
if (_delegateHasDoubleTapOnMap)
[delegate doubleTapOnMap:self at:aPoint];
}
- (void)setMapCenterCoordinate:(CLLocationCoordinate2D)center
- (void)mapTiledLayerView:(RMMapTiledLayerView *)aTiledLayerView twoFingerDoubleTapAtPoint:(CGPoint)aPoint
{
[self moveToCoordinate:center];
[self zoomOutToNextNativeZoomAt:aPoint animated:YES];
if (_delegateHasDoubleTapTwoFingersOnMap)
[delegate doubleTapTwoFingersOnMap:self at:aPoint];
}
- (RMProjectedPoint)mapCenterProjectedPoint
- (void)mapTiledLayerView:(RMMapTiledLayerView *)aTiledLayerView longPressAtPoint:(CGPoint)aPoint
{
return [mercatorToScreenProjection projectedCenter];
if (_delegateHasLongSingleTapOnMap)
[delegate longSingleTapOnMap:self at:aPoint];
}
- (void)setMapCenterProjectedPoint:(RMProjectedPoint)projectedPoint
// Detect dragging/zooming
- (void)observeValueForKeyPath:(NSString *)aKeyPath ofObject:(id)anObject change:(NSDictionary *)change context:(void *)context
{
if (![self tileSourceBoundsContainProjectedPoint:projectedPoint])
return;
if (anObject != mapScrollView) return;
[mercatorToScreenProjection setProjectedCenter:projectedPoint];
[tileLoader reload];
[self correctPositionOfAllAnnotations];
RMProjectedRect planetBounds = projection.planetBounds;
metersPerPixel = planetBounds.size.width / mapScrollView.contentSize.width;
// TODO: Use RMTranslateCGPointBy
[self correctPositionOfAllAnnotationsIncludingInvisibles:YES];
if (_delegateHasMapViewRegionDidChange) [delegate mapViewRegionDidChange:self];
}
- (RMProjectedRect)projectedBounds
#pragma mark -
#pragma mark Properties
- (id <RMTileSource>)tileSource
{
return [mercatorToScreenProjection projectedBounds];
return [[tileSource retain] autorelease];
}
- (void)setProjectedBounds:(RMProjectedRect)boundsRect
- (void)setTileSource:(id <RMTileSource>)newTileSource
{
[mercatorToScreenProjection setProjectedBounds:boundsRect];
[tileLoader reload];
[self correctPositionOfAllAnnotations];
if (tileSource == newTileSource)
return;
[self setMinZoom:newTileSource.minZoom];
[self setMaxZoom:newTileSource.maxZoom];
[self setZoom:[self zoom]]; // setZoom clamps zoom level to min/max limits
[tileSource autorelease];
tileSource = [newTileSource retain];
[projection release];
projection = [[tileSource projection] retain];
[mercatorToTileProjection release];
mercatorToTileProjection = [[tileSource mercatorToTileProjection] retain];
tileSourceProjectedBounds = (RMProjectedRect)[self projectedRectFromLatitudeLongitudeBounds:[tileSource latitudeLongitudeBoundingBox]];
}
- (RMTileRect)tileBounds
- (UIView *)backgroundView
{
return [mercatorToTileProjection projectRect:[mercatorToScreenProjection projectedBounds] atScale:[self scaledMetersPerPixel]];
return [[backgroundView retain] autorelease];
}
- (CGRect)screenBounds
- (void)setBackgroundView:(UIView *)aView
{
if (mercatorToScreenProjection != nil)
return [mercatorToScreenProjection screenBounds];
else
return CGRectZero;
if (backgroundView == aView)
return;
if (backgroundView != nil) {
[backgroundView removeFromSuperview];
[backgroundView release];
}
backgroundView = [aView retain];
if (backgroundView == nil)
return;
backgroundView.frame = [self bounds];
[self insertSubview:backgroundView atIndex:0];
}
- (float)metersPerPixel
- (double)metersPerPixel
{
return [mercatorToScreenProjection metersPerPixel];
return metersPerPixel;
}
- (void)setMetersPerPixel:(float)newMPP
- (void)setMetersPerPixel:(double)newMetersPerPixel
{
float zoomFactor = self.metersPerPixel / newMPP;
CGPoint pivot = CGPointZero;
[mercatorToScreenProjection setMetersPerPixel:newMPP];
[imagesOnScreen zoomByFactor:zoomFactor near:pivot];
[tileLoader zoomByFactor:zoomFactor near:pivot];
// [mercatorToScreenProjection setMetersPerPixel:newMPP];
[self correctPositionOfAllAnnotations];
}
- (float)scaledMetersPerPixel
- (double)scaledMetersPerPixel
{
return [mercatorToScreenProjection metersPerPixel] / screenScale;
return self.metersPerPixel / screenScale;
}
- (double)scaleDenominator
{
double routemeMetersPerPixel = [self metersPerPixel];
double routemeMetersPerPixel = self.metersPerPixel;
double iphoneMillimetersPerPixel = kiPhoneMilimeteresPerPixel;
double truescaleDenominator = routemeMetersPerPixel / (0.001 * iphoneMillimetersPerPixel);
double truescaleDenominator = routemeMetersPerPixel / (0.001 * iphoneMillimetersPerPixel);
return truescaleDenominator;
}
- (void)setMaxZoom:(float)newMaxZoom
{
maxZoom = newMaxZoom;
}
- (void)setMinZoom:(float)newMinZoom
{
minZoom = newMinZoom;
mapScrollView.minimumZoomScale = 1<<(int)(newMinZoom - 1.0);
}
if (!tileSource || (([tileSource minZoom] - minZoom) <= 1.0)) {
RMLog(@"Graphics & memory are overly taxed if [contents minZoom] is more than 1.5 smaller than [tileSource minZoom]");
}
- (void)setMaxZoom:(float)newMaxZoom
{
maxZoom = newMaxZoom;
mapScrollView.maximumZoomScale = 1<<(int)(newMaxZoom - 1.0);
}
- (float)zoom
{
return [mercatorToTileProjection calculateZoomFromScale:[mercatorToScreenProjection metersPerPixel]];
return log2f(mapScrollView.zoomScale) + 1.0;
}
// if #zoom is outside of range #minZoom to #maxZoom, zoom level is clamped to that range.
... ... @@ -1270,18 +1131,23 @@ double CubicEaseOut(double t, double start, double end)
zoom = (zoom > maxZoom) ? maxZoom : zoom;
zoom = (zoom < minZoom) ? minZoom : zoom;
float scale = [mercatorToTileProjection calculateScaleFromZoom:zoom];
[self setMetersPerPixel:scale];
mapScrollView.zoomScale = 1<<(int)(zoom - 1.0);
}
- (void)setEnableClustering:(BOOL)doEnableClustering
{
enableClustering = doEnableClustering;
[self correctPositionOfAllAnnotations];
}
- (RMTileImageSet *)imagesOnScreen
- (RMMapDecelerationMode)decelerationMode
{
return [[imagesOnScreen retain] autorelease];
return (mapScrollView.decelerationRate == UIScrollViewDecelerationRateNormal ? RMMapDecelerationNormal : RMMapDecelerationFast);
}
- (RMTileLoader *)tileLoader
- (void)setDecelerationMode:(RMMapDecelerationMode)aDecelerationMode
{
return [[tileLoader retain] autorelease];
[mapScrollView setDecelerationRate:(aDecelerationMode == RMMapDecelerationNormal ? UIScrollViewDecelerationRateNormal : UIScrollViewDecelerationRateFast)];
}
- (RMProjection *)projection
... ... @@ -1294,51 +1160,47 @@ double CubicEaseOut(double t, double start, double end)
return [[mercatorToTileProjection retain] autorelease];
}
- (RMMercatorToScreenProjection *)mercatorToScreenProjection
{
return [[mercatorToScreenProjection retain] autorelease];
}
- (void)setEnableClustering:(BOOL)doEnableClustering
{
enableClustering = doEnableClustering;
[self correctPositionOfAllAnnotations];
}
#pragma mark -
#pragma mark LatLng/Pixel translation functions
- (CGPoint)coordinateToPixel:(CLLocationCoordinate2D)latlong
- (CGPoint)projectedPointToPixel:(RMProjectedPoint)projectedPoint
{
return [mercatorToScreenProjection projectProjectedPoint:[projection coordinateToProjectedPoint:latlong]];
}
RMProjectedRect planetBounds = projection.planetBounds;
RMProjectedPoint normalizedProjectedPoint;
normalizedProjectedPoint.x = projectedPoint.x + fabs(planetBounds.origin.x);
normalizedProjectedPoint.y = projectedPoint.y + fabs(planetBounds.origin.y);
- (CGPoint)coordinateToPixel:(CLLocationCoordinate2D)latlong withMetersPerPixel:(float)aScale
{
return [mercatorToScreenProjection projectProjectedPoint:[projection coordinateToProjectedPoint:latlong] withMetersPerPixel:aScale];
CGPoint projectedPixel = CGPointMake((normalizedProjectedPoint.x / self.metersPerPixel) - mapScrollView.contentOffset.x, (mapScrollView.contentSize.height - (normalizedProjectedPoint.y / self.metersPerPixel)) - mapScrollView.contentOffset.y);
return projectedPixel;
}
- (RMTilePoint)coordinateToTilePoint:(CLLocationCoordinate2D)latlong withMetersPerPixel:(float)aScale
- (CGPoint)coordinateToPixel:(CLLocationCoordinate2D)coordinate
{
return [mercatorToTileProjection project:[projection coordinateToProjectedPoint:latlong] atZoom:aScale];
return [self projectedPointToPixel:[projection coordinateToProjectedPoint:coordinate]];
}
- (CLLocationCoordinate2D)pixelToCoordinate:(CGPoint)aPixel
- (RMProjectedPoint)pixelToProjectedPoint:(CGPoint)pixelCoordinate
{
return [projection projectedPointToCoordinate:[mercatorToScreenProjection projectScreenPointToProjectedPoint:aPixel]];
RMProjectedRect planetBounds = projection.planetBounds;
RMProjectedPoint normalizedProjectedPoint;
normalizedProjectedPoint.x = ((pixelCoordinate.x + mapScrollView.contentOffset.x) * self.metersPerPixel) - fabs(planetBounds.origin.x);
normalizedProjectedPoint.y = ((mapScrollView.contentSize.height - mapScrollView.contentOffset.y - pixelCoordinate.y) * self.metersPerPixel) - fabs(planetBounds.origin.y);
return normalizedProjectedPoint;
}
- (CLLocationCoordinate2D)pixelToCoordinate:(CGPoint)aPixel withMetersPerPixel:(float)aScale
- (CLLocationCoordinate2D)pixelToCoordinate:(CGPoint)pixelCoordinate
{
return [projection projectedPointToCoordinate:[mercatorToScreenProjection projectScreenPointToProjectedPoint:aPixel withMetersPerPixel:aScale]];
return [projection projectedPointToCoordinate:[self pixelToProjectedPoint:pixelCoordinate]];
}
#pragma mark -
#pragma mark Markers and overlays
- (RMSphericalTrapezium)latitudeLongitudeBoundingBoxForScreen
- (RMSphericalTrapezium)latitudeLongitudeBoundingBox
{
return [self latitudeLongitudeBoundingBoxFor:[mercatorToScreenProjection screenBounds]];
return [self latitudeLongitudeBoundingBoxFor:[self bounds]];
}
- (RMSphericalTrapezium)latitudeLongitudeBoundingBoxFor:(CGRect)rect
... ... @@ -1381,32 +1243,18 @@ double CubicEaseOut(double t, double start, double end)
return boundingBox;
}
- (void)printDebuggingInformation
{
[imagesOnScreen printDebuggingInformation];
}
- (short)tileDepth
{
return imagesOnScreen.tileDepth;
}
- (void)setTileDepth:(short)value
{
imagesOnScreen.tileDepth = value;
}
- (BOOL)fullyLoaded
{
return imagesOnScreen.fullyLoaded;
}
#pragma mark -
#pragma mark Annotations
- (void)correctScreenPosition:(RMAnnotation *)annotation
{
annotation.position = [[self mercatorToScreenProjection] projectProjectedPoint:[annotation projectedLocation]];
RMProjectedRect planetBounds = projection.planetBounds;
RMProjectedPoint normalizedProjectedPoint;
normalizedProjectedPoint.x = annotation.projectedLocation.x + fabs(planetBounds.origin.x);
normalizedProjectedPoint.y = annotation.projectedLocation.y + fabs(planetBounds.origin.y);
annotation.position = CGPointMake((normalizedProjectedPoint.x / self.metersPerPixel) - mapScrollView.contentOffset.x, mapScrollView.contentSize.height - (normalizedProjectedPoint.y / self.metersPerPixel) - mapScrollView.contentOffset.y);
// NSLog(@"Change annotation at {%f,%f} in mapView {%f,%f}", annotation.position.x, annotation.position.y, mapScrollView.contentSize.width, mapScrollView.contentSize.height);
}
- (void)correctPositionOfAllAnnotationsIncludingInvisibles:(BOOL)correctAllAnnotations
... ... @@ -1422,15 +1270,14 @@ double CubicEaseOut(double t, double start, double end)
{
[self correctScreenPosition:annotation];
}
// RMLog(@"%d annotations corrected", [visibleAnnotations count]);
// RMLog(@"%d annotations corrected", [visibleAnnotations count]);
[CATransaction commit];
return;
}
RMProjectedRect boundingBox = [[self mercatorToScreenProjection] projectedBounds];
float metersPerPixel = self.metersPerPixel;
NSArray *annotationsToCorrect = [quadTree annotationsInProjectedRect:boundingBox createClusterAnnotations:self.enableClustering withClusterSize:RMProjectedSizeMake(self.clusterMarkerSize.width * metersPerPixel, self.clusterMarkerSize.height * metersPerPixel) findGravityCenter:self.positionClusterMarkersAtTheGravityCenter];
RMProjectedRect boundingBox = [self projectedBounds];
NSArray *annotationsToCorrect = [quadTree annotationsInProjectedRect:boundingBox createClusterAnnotations:self.enableClustering withClusterSize:RMProjectedSizeMake(self.clusterMarkerSize.width * self.metersPerPixel, self.clusterMarkerSize.height * self.metersPerPixel) findGravityCenter:self.positionClusterMarkersAtTheGravityCenter];
NSMutableSet *previousVisibleAnnotations = [NSMutableSet setWithSet:visibleAnnotations];
for (RMAnnotation *annotation in annotationsToCorrect)
... ... @@ -1444,7 +1291,7 @@ double CubicEaseOut(double t, double start, double end)
// Use the zPosition property to order the layer hierarchy
if (![visibleAnnotations containsObject:annotation]) {
[overlay addSublayer:annotation.layer];
[overlayView addSublayer:annotation.layer];
[visibleAnnotations addObject:annotation];
}
[previousVisibleAnnotations removeObject:annotation];
... ... @@ -1458,12 +1305,11 @@ double CubicEaseOut(double t, double start, double end)
if (_delegateHasDidHideLayerForAnnotation) [delegate mapView:self didHideLayerForAnnotation:annotation];
}
// RMLog(@"%d annotations on screen, %d total", [[overlay sublayers] count], [annotations count]);
// RMLog(@"%d annotations on screen, %d total", [overlayView sublayersCount], [annotations count]);
} else
{
CALayer *lastLayer = nil;
CGRect screenBounds = [[self mercatorToScreenProjection] screenBounds];
@synchronized (annotations)
{
... ... @@ -1472,7 +1318,7 @@ double CubicEaseOut(double t, double start, double end)
for (RMAnnotation *annotation in annotations)
{
[self correctScreenPosition:annotation];
if ([annotation isAnnotationWithinBounds:screenBounds]) {
if ([annotation isAnnotationWithinBounds:[self bounds]]) {
if (annotation.layer == nil && _delegateHasLayerForAnnotation)
annotation.layer = [delegate mapView:self layerForAnnotation:annotation];
if (annotation.layer == nil)
... ... @@ -1480,9 +1326,9 @@ double CubicEaseOut(double t, double start, double end)
if (![visibleAnnotations containsObject:annotation]) {
if (!lastLayer)
[overlay insertSublayer:annotation.layer atIndex:0];
[overlayView insertSublayer:annotation.layer atIndex:0];
else
[overlay insertSublayer:annotation.layer above:lastLayer];
[overlayView insertSublayer:annotation.layer above:lastLayer];
[visibleAnnotations addObject:annotation];
}
... ... @@ -1494,13 +1340,13 @@ double CubicEaseOut(double t, double start, double end)
if (_delegateHasDidHideLayerForAnnotation) [delegate mapView:self didHideLayerForAnnotation:annotation];
}
}
// RMLog(@"%d annotations on screen, %d total", [[overlay sublayers] count], [annotations count]);
// RMLog(@"%d annotations on screen, %d total", [overlayView sublayersCount], [annotations count]);
} else {
for (RMAnnotation *annotation in visibleAnnotations)
{
[self correctScreenPosition:annotation];
}
// RMLog(@"%d annotations corrected", [visibleAnnotations count]);
// RMLog(@"%d annotations corrected", [visibleAnnotations count]);
}
}
}
... ... @@ -1535,7 +1381,7 @@ double CubicEaseOut(double t, double start, double end)
{
annotation.layer = [delegate mapView:self layerForAnnotation:annotation];
if (annotation.layer) {
[overlay addSublayer:annotation.layer];
[overlayView addSublayer:annotation.layer];
[visibleAnnotations addObject:annotation];
}
}
... ... @@ -1603,338 +1449,293 @@ double CubicEaseOut(double t, double start, double end)
return annotation.position;
}
#pragma mark -
#pragma mark Event handling
- (RMGestureDetails)gestureDetails:(NSSet *)touches
{
RMGestureDetails gesture;
gesture.center.x = gesture.center.y = 0;
gesture.averageDistanceFromCenter = 0;
gesture.angle = 0.0;
int interestingTouches = 0;
for (UITouch *touch in touches)
{
if ([touch phase] != UITouchPhaseBegan
&& [touch phase] != UITouchPhaseMoved
&& [touch phase] != UITouchPhaseStationary)
continue;
// RMLog(@"phase = %d", [touch phase]);
interestingTouches++;
CGPoint location = [touch locationInView: self];
gesture.center.x += location.x;
gesture.center.y += location.y;
}
if (interestingTouches == 0)
{
gesture.center = lastGesture.center;
gesture.numTouches = 0;
gesture.averageDistanceFromCenter = 0.0f;
return gesture;
}
// RMLog(@"interestingTouches = %d", interestingTouches);
gesture.center.x /= interestingTouches;
gesture.center.y /= interestingTouches;
for (UITouch *touch in touches)
{
if ([touch phase] != UITouchPhaseBegan
&& [touch phase] != UITouchPhaseMoved
&& [touch phase] != UITouchPhaseStationary)
continue;
CGPoint location = [touch locationInView: self];
// RMLog(@"For touch at %.0f, %.0f:", location.x, location.y);
float dx = location.x - gesture.center.x;
float dy = location.y - gesture.center.y;
// RMLog(@"delta = %.0f, %.0f distance = %f", dx, dy, sqrtf((dx*dx) + (dy*dy)));
gesture.averageDistanceFromCenter += sqrtf((dx*dx) + (dy*dy));
}
gesture.averageDistanceFromCenter /= interestingTouches;
gesture.numTouches = interestingTouches;
if ([touches count] == 2)
{
CGPoint first = [[[touches allObjects] objectAtIndex:0] locationInView:[self superview]];
CGPoint second = [[[touches allObjects] objectAtIndex:1] locationInView:[self superview]];
CGFloat height = second.y - first.y;
CGFloat width = first.x - second.x;
gesture.angle = atan2(height,width);
}
return gesture;
}
- (void)handleLongPress
{
if (deceleration && _decelerationTimer != nil)
return;
if (_delegateHasLongSingleTapOnMap)
[delegate longSingleTapOnMap:self at:_longPressPosition];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[touches allObjects] objectAtIndex:0];
//Check if the touch hit a RMMarker subclass and if so, forward the touch event on
//so it can be handled there
id furthestLayerDown = [self.overlay hitTest:[touch locationInView:self]];
if ([[furthestLayerDown class] isSubclassOfClass:[RMMarker class]]) {
if ([furthestLayerDown respondsToSelector:@selector(touchesBegan:withEvent:)]) {
[furthestLayerDown performSelector:@selector(touchesBegan:withEvent:) withObject:touches withObject:event];
return;
}
}
// RMLog(@"touchesBegan %d", [[event allTouches] count]);
lastGesture = [self gestureDetails:[event allTouches]];
if (deceleration && _decelerationTimer != nil) {
[self stopDeceleration];
}
_longPressPosition = lastGesture.center;
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleLongPress) object:nil];
if (lastGesture.numTouches == 1) {
CALayer* hit = [self.overlay hitTest:[touch locationInView:self]];
if (!hit || ![hit isKindOfClass: [RMMarker class]]) {
[self performSelector:@selector(handleLongPress) withObject:nil afterDelay:0.5];
}
}
}
// \bug touchesCancelled should clean up, not pass event to markers
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[touches allObjects] objectAtIndex:0];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleLongPress) object:nil];
// Check if the touch hit a RMMarker subclass and if so, forward the touch event on
// so it can be handled there
id furthestLayerDown = [self.overlay hitTest:[touch locationInView:self]];
if ([[furthestLayerDown class]isSubclassOfClass: [RMMarker class]]) {
if ([furthestLayerDown respondsToSelector:@selector(touchesCancelled:withEvent:)]) {
[furthestLayerDown performSelector:@selector(touchesCancelled:withEvent:) withObject:touches withObject:event];
return;
}
}
// I don't understand what the difference between this and touchesEnded is.
[self touchesEnded:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[touches allObjects] objectAtIndex:0];
//Check if the touch hit a RMMarker subclass and if so, forward the touch event on
//so it can be handled there
id furthestLayerDown = [self.overlay hitTest:[touch locationInView:self]];
if ([[furthestLayerDown class]isSubclassOfClass: [RMMarker class]]) {
if ([furthestLayerDown respondsToSelector:@selector(touchesEnded:withEvent:)]) {
[furthestLayerDown performSelector:@selector(touchesEnded:withEvent:) withObject:touches withObject:event];
return;
}
}
NSInteger lastTouches = lastGesture.numTouches;
// Calculate the gesture.
lastGesture = [self gestureDetails:[event allTouches]];
BOOL decelerating = NO;
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleLongPress) object:nil];
if (touch.tapCount >= 2)
{
BOOL twoFingerTap = [touches count] >= 2;
if (_delegateHasDoubleTapOnMap) {
if (twoFingerTap) {
if (_delegateHasDoubleTapTwoFingersOnMap) [delegate doubleTapTwoFingersOnMap:self at:lastGesture.center];
} else {
if (_delegateHasDoubleTapOnMap) [delegate doubleTapOnMap: self at:lastGesture.center];
}
} else {
// Default behaviour matches built in maps.app
float nextZoomFactor = 0;
if (twoFingerTap) {
nextZoomFactor = [self previousNativeZoomFactor];
} else {
nextZoomFactor = [self nextNativeZoomFactor];
}
if (nextZoomFactor != 0)
[self zoomByFactor:nextZoomFactor near:[touch locationInView:self] animated:YES];
}
} else if (lastTouches == 1 && touch.tapCount != 1) {
// deceleration
if (deceleration && enableDragging)
{
CGPoint prevLocation = [touch previousLocationInView:self];
CGPoint currLocation = [touch locationInView:self];
CGSize touchDelta = CGSizeMake(currLocation.x - prevLocation.x, currLocation.y - prevLocation.y);
[self startDecelerationWithDelta:touchDelta];
decelerating = YES;
}
}
if (touch.tapCount == 1)
{
if (lastGesture.numTouches == 0)
{
CALayer *hit = [self.overlay hitTest:[touch locationInView:self]];
// RMLog(@"LAYER of type %@",[hit description]);
if (hit != nil)
{
CALayer *superlayer = [hit superlayer];
// See if tap was on a marker or marker label and send delegate protocol method
if ([hit isKindOfClass:[RMMarker class]]) {
if (_delegateHasTapOnMarker) {
[delegate tapOnAnnotation:((RMMarker *)hit).annotation onMap:self];
}
} else if (superlayer != nil && [superlayer isKindOfClass: [RMMarker class]]) {
if (_delegateHasTapOnLabelForMarker) {
[delegate tapOnLabelForAnnotation:((RMMarker *)superlayer).annotation onMap:self];
}
} else if ([superlayer superlayer] != nil && [[superlayer superlayer] isKindOfClass:[RMMarker class]]) {
if (_delegateHasTapOnLabelForMarker) {
[delegate tapOnLabelForAnnotation:((RMMarker *)[superlayer superlayer]).annotation onMap:self];
}
} else if (_delegateHasSingleTapOnMap) {
[delegate singleTapOnMap:self at:[touch locationInView:self]];
}
}
}
else if (!enableDragging && (lastGesture.numTouches == 1))
{
float prevZoomFactor = [self previousNativeZoomFactor];
if (prevZoomFactor != 0)
[self zoomByFactor:prevZoomFactor near:[touch locationInView:self] animated:YES];
}
}
if (_delegateHasAfterMapTouch) [delegate afterMapTouch:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[touches allObjects] objectAtIndex:0];
//Check if the touch hit a RMMarker subclass and if so, forward the touch event on
//so it can be handled there
id furthestLayerDown = [self.overlay hitTest:[touch locationInView:self]];
if ([[furthestLayerDown class]isSubclassOfClass: [RMMarker class]]) {
if ([furthestLayerDown respondsToSelector:@selector(touchesMoved:withEvent:)]) {
[furthestLayerDown performSelector:@selector(touchesMoved:withEvent:) withObject:touches withObject:event];
return;
}
}
RMGestureDetails newGesture = [self gestureDetails:[event allTouches]];
CGPoint newLongPressPosition = newGesture.center;
CGFloat dx = newLongPressPosition.x - _longPressPosition.x;
CGFloat dy = newLongPressPosition.y - _longPressPosition.y;
if (sqrt(dx*dx + dy*dy) > 5.0)
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleLongPress) object:nil];
CALayer *hit = [self.overlay hitTest:[touch locationInView:self]];
// RMLog(@"LAYER of type %@",[hit description]);
if (hit != nil)
{
if ([hit isKindOfClass: [RMMarker class]]) {
if (!_delegateHasShouldDragMarker || (_delegateHasShouldDragMarker && [delegate mapView:self shouldDragAnnotation:((RMMarker *)hit).annotation withEvent:event]))
{
if (_delegateHasDidDragMarker) {
[delegate mapView:self didDragAnnotation:((RMMarker *)hit).annotation withEvent:event];
return;
}
}
}
}
if (newGesture.numTouches == lastGesture.numTouches)
{
CGSize delta;
delta.width = newGesture.center.x - lastGesture.center.x;
delta.height = newGesture.center.y - lastGesture.center.y;
if (enableZoom && newGesture.numTouches > 1)
{
NSAssert (lastGesture.averageDistanceFromCenter > 0.0f && newGesture.averageDistanceFromCenter > 0.0f,
@"Distance from center is zero despite >1 touches on the screen");
double zoomFactor = newGesture.averageDistanceFromCenter / lastGesture.averageDistanceFromCenter;
[self moveBy:delta];
[self zoomByFactor:zoomFactor near:newGesture.center];
}
else if (enableDragging)
{
[self moveBy:delta];
}
}
lastGesture = newGesture;
}
#pragma mark Deceleration
- (void)startDecelerationWithDelta:(CGSize)delta
{
if (fabsf(delta.width) >= 1.0f && fabsf(delta.height) >= 1.0f)
{
_decelerationDelta = delta;
if ( !_decelerationTimer ) {
_decelerationTimer = [NSTimer scheduledTimerWithTimeInterval:kDecelerationTimerInterval
target:self
selector:@selector(incrementDeceleration:)
userInfo:nil
repeats:YES];
}
}
}
- (void)incrementDeceleration:(NSTimer *)timer
{
if (fabsf(_decelerationDelta.width) < kMinDecelerationDelta && fabsf(_decelerationDelta.height) < kMinDecelerationDelta) {
[self stopDeceleration];
return;
}
// avoid calling delegate methods? design call here
[self moveBy:_decelerationDelta isAnimationStep:YES];
_decelerationDelta.width *= self.decelerationFactor;
_decelerationDelta.height *= self.decelerationFactor;
}
- (void)stopDeceleration
{
if (_decelerationTimer != nil) {
[_decelerationTimer invalidate]; _decelerationTimer = nil;
_decelerationDelta = CGSizeZero;
// call delegate methods; design call (see above)
[self moveBy:CGSizeZero];
}
if (_delegateHasAfterMapMoveDeceleration)
[delegate afterMapMoveDeceleration:self];
}
//#pragma mark -
//#pragma mark Event handling
//
//- (RMGestureDetails)gestureDetails:(NSSet *)touches
//{
// RMGestureDetails gesture;
// gesture.center.x = gesture.center.y = 0;
// gesture.averageDistanceFromCenter = 0;
// gesture.angle = 0.0;
//
// int interestingTouches = 0;
//
// for (UITouch *touch in touches)
// {
// if ([touch phase] != UITouchPhaseBegan
// && [touch phase] != UITouchPhaseMoved
// && [touch phase] != UITouchPhaseStationary)
// continue;
// // RMLog(@"phase = %d", [touch phase]);
//
// interestingTouches++;
//
// CGPoint location = [touch locationInView: self];
//
// gesture.center.x += location.x;
// gesture.center.y += location.y;
// }
//
// if (interestingTouches == 0)
// {
// gesture.center = lastGesture.center;
// gesture.numTouches = 0;
// gesture.averageDistanceFromCenter = 0.0f;
// return gesture;
// }
//
// // RMLog(@"interestingTouches = %d", interestingTouches);
//
// gesture.center.x /= interestingTouches;
// gesture.center.y /= interestingTouches;
//
// for (UITouch *touch in touches)
// {
// if ([touch phase] != UITouchPhaseBegan
// && [touch phase] != UITouchPhaseMoved
// && [touch phase] != UITouchPhaseStationary)
// continue;
//
// CGPoint location = [touch locationInView: self];
//
// // RMLog(@"For touch at %.0f, %.0f:", location.x, location.y);
// float dx = location.x - gesture.center.x;
// float dy = location.y - gesture.center.y;
// // RMLog(@"delta = %.0f, %.0f distance = %f", dx, dy, sqrtf((dx*dx) + (dy*dy)));
// gesture.averageDistanceFromCenter += sqrtf((dx*dx) + (dy*dy));
// }
//
// gesture.averageDistanceFromCenter /= interestingTouches;
// gesture.numTouches = interestingTouches;
//
// if ([touches count] == 2)
// {
// CGPoint first = [[[touches allObjects] objectAtIndex:0] locationInView:[self superview]];
// CGPoint second = [[[touches allObjects] objectAtIndex:1] locationInView:[self superview]];
// CGFloat height = second.y - first.y;
// CGFloat width = first.x - second.x;
// gesture.angle = atan2(height,width);
// }
//
// return gesture;
//}
//
//- (void)handleLongPress
//{
// if (deceleration && _decelerationTimer != nil)
// return;
//
// if (_delegateHasLongSingleTapOnMap)
// [delegate longSingleTapOnMap:self at:_longPressPosition];
//}
//
//- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
//{
// UITouch *touch = [[touches allObjects] objectAtIndex:0];
// //Check if the touch hit a RMMarker subclass and if so, forward the touch event on
// //so it can be handled there
// id furthestLayerDown = [self.overlay hitTest:[touch locationInView:self]];
// if ([[furthestLayerDown class] isSubclassOfClass:[RMMarker class]]) {
// if ([furthestLayerDown respondsToSelector:@selector(touchesBegan:withEvent:)]) {
// [furthestLayerDown performSelector:@selector(touchesBegan:withEvent:) withObject:touches withObject:event];
// return;
// }
// }
//
// // RMLog(@"touchesBegan %d", [[event allTouches] count]);
// lastGesture = [self gestureDetails:[event allTouches]];
//
// if (deceleration && _decelerationTimer != nil) {
// [self stopDeceleration];
// }
//
// _longPressPosition = lastGesture.center;
// [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleLongPress) object:nil];
//
// if (lastGesture.numTouches == 1) {
// CALayer* hit = [self.overlay hitTest:[touch locationInView:self]];
// if (!hit || ![hit isKindOfClass: [RMMarker class]]) {
// [self performSelector:@selector(handleLongPress) withObject:nil afterDelay:0.5];
// }
// }
//}
//
//// \bug touchesCancelled should clean up, not pass event to markers
//- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
//{
// UITouch *touch = [[touches allObjects] objectAtIndex:0];
//
// [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleLongPress) object:nil];
//
// // Check if the touch hit a RMMarker subclass and if so, forward the touch event on
// // so it can be handled there
// id furthestLayerDown = [self.overlay hitTest:[touch locationInView:self]];
// if ([[furthestLayerDown class]isSubclassOfClass: [RMMarker class]]) {
// if ([furthestLayerDown respondsToSelector:@selector(touchesCancelled:withEvent:)]) {
// [furthestLayerDown performSelector:@selector(touchesCancelled:withEvent:) withObject:touches withObject:event];
// return;
// }
// }
//
// // I don't understand what the difference between this and touchesEnded is.
// [self touchesEnded:touches withEvent:event];
//}
//
//- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
//{
// UITouch *touch = [[touches allObjects] objectAtIndex:0];
//
// //Check if the touch hit a RMMarker subclass and if so, forward the touch event on
// //so it can be handled there
// id furthestLayerDown = [self.overlay hitTest:[touch locationInView:self]];
// if ([[furthestLayerDown class]isSubclassOfClass: [RMMarker class]]) {
// if ([furthestLayerDown respondsToSelector:@selector(touchesEnded:withEvent:)]) {
// [furthestLayerDown performSelector:@selector(touchesEnded:withEvent:) withObject:touches withObject:event];
// return;
// }
// }
// NSInteger lastTouches = lastGesture.numTouches;
//
// // Calculate the gesture.
// lastGesture = [self gestureDetails:[event allTouches]];
//
// BOOL decelerating = NO;
//
// [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleLongPress) object:nil];
//
// if (touch.tapCount >= 2)
// {
// BOOL twoFingerTap = [touches count] >= 2;
// if (_delegateHasDoubleTapOnMap) {
// if (twoFingerTap) {
// if (_delegateHasDoubleTapTwoFingersOnMap) [delegate doubleTapTwoFingersOnMap:self at:lastGesture.center];
// } else {
// if (_delegateHasDoubleTapOnMap) [delegate doubleTapOnMap: self at:lastGesture.center];
// }
// } else {
// // Default behaviour matches built in maps.app
// float nextZoomFactor = 0;
// if (twoFingerTap) {
// nextZoomFactor = [self previousNativeZoomFactor];
// } else {
// nextZoomFactor = [self nextNativeZoomFactor];
// }
// if (nextZoomFactor != 0)
// [self zoomByFactor:nextZoomFactor near:[touch locationInView:self] animated:YES];
// }
// } else if (lastTouches == 1 && touch.tapCount != 1) {
// // deceleration
// if (deceleration && enableDragging)
// {
// CGPoint prevLocation = [touch previousLocationInView:self];
// CGPoint currLocation = [touch locationInView:self];
// CGSize touchDelta = CGSizeMake(currLocation.x - prevLocation.x, currLocation.y - prevLocation.y);
// [self startDecelerationWithDelta:touchDelta];
// decelerating = YES;
// }
// }
//
// if (touch.tapCount == 1)
// {
// if (lastGesture.numTouches == 0)
// {
// CALayer *hit = [self.overlay hitTest:[touch locationInView:self]];
// // RMLog(@"LAYER of type %@",[hit description]);
//
// if (hit != nil)
// {
// CALayer *superlayer = [hit superlayer];
//
// // See if tap was on a marker or marker label and send delegate protocol method
// if ([hit isKindOfClass:[RMMarker class]]) {
// if (_delegateHasTapOnMarker) {
// [delegate tapOnAnnotation:((RMMarker *)hit).annotation onMap:self];
// }
// } else if (superlayer != nil && [superlayer isKindOfClass: [RMMarker class]]) {
// if (_delegateHasTapOnLabelForMarker) {
// [delegate tapOnLabelForAnnotation:((RMMarker *)superlayer).annotation onMap:self];
// }
// } else if ([superlayer superlayer] != nil && [[superlayer superlayer] isKindOfClass:[RMMarker class]]) {
// if (_delegateHasTapOnLabelForMarker) {
// [delegate tapOnLabelForAnnotation:((RMMarker *)[superlayer superlayer]).annotation onMap:self];
// }
// } else if (_delegateHasSingleTapOnMap) {
// [delegate singleTapOnMap:self at:[touch locationInView:self]];
// }
// }
// }
// else if (!enableDragging && (lastGesture.numTouches == 1))
// {
// float prevZoomFactor = [self previousNativeZoomFactor];
// if (prevZoomFactor != 0)
// [self zoomByFactor:prevZoomFactor near:[touch locationInView:self] animated:YES];
// }
// }
//
// if (_delegateHasAfterMapTouch) [delegate afterMapTouch:self];
//}
//
//- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
//{
// UITouch *touch = [[touches allObjects] objectAtIndex:0];
//
// //Check if the touch hit a RMMarker subclass and if so, forward the touch event on
// //so it can be handled there
// id furthestLayerDown = [self.overlay hitTest:[touch locationInView:self]];
// if ([[furthestLayerDown class]isSubclassOfClass: [RMMarker class]]) {
// if ([furthestLayerDown respondsToSelector:@selector(touchesMoved:withEvent:)]) {
// [furthestLayerDown performSelector:@selector(touchesMoved:withEvent:) withObject:touches withObject:event];
// return;
// }
// }
//
// RMGestureDetails newGesture = [self gestureDetails:[event allTouches]];
// CGPoint newLongPressPosition = newGesture.center;
// CGFloat dx = newLongPressPosition.x - _longPressPosition.x;
// CGFloat dy = newLongPressPosition.y - _longPressPosition.y;
// if (sqrt(dx*dx + dy*dy) > 5.0)
// [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleLongPress) object:nil];
//
// CALayer *hit = [self.overlay hitTest:[touch locationInView:self]];
//// RMLog(@"LAYER of type %@",[hit description]);
//
// if (hit != nil)
// {
// if ([hit isKindOfClass: [RMMarker class]]) {
// if (!_delegateHasShouldDragMarker || (_delegateHasShouldDragMarker && [delegate mapView:self shouldDragAnnotation:((RMMarker *)hit).annotation withEvent:event]))
// {
// if (_delegateHasDidDragMarker) {
// [delegate mapView:self didDragAnnotation:((RMMarker *)hit).annotation withEvent:event];
// return;
// }
// }
// }
// }
//
// if (newGesture.numTouches == lastGesture.numTouches)
// {
// CGSize delta;
// delta.width = newGesture.center.x - lastGesture.center.x;
// delta.height = newGesture.center.y - lastGesture.center.y;
//
// if (enableZoom && newGesture.numTouches > 1)
// {
// NSAssert (lastGesture.averageDistanceFromCenter > 0.0f && newGesture.averageDistanceFromCenter > 0.0f,
// @"Distance from center is zero despite >1 touches on the screen");
//
// double zoomFactor = newGesture.averageDistanceFromCenter / lastGesture.averageDistanceFromCenter;
//
// [self moveBy:delta];
// [self zoomByFactor:zoomFactor near:newGesture.center];
// }
// else if (enableDragging)
// {
// [self moveBy:delta];
// }
// }
//
// lastGesture = newGesture;
//}
@end
... ...
... ... @@ -40,10 +40,9 @@
- (void)beforeMapMove:(RMMapView *)map;
- (void)afterMapMove:(RMMapView *)map;
- (void)afterMapMoveDeceleration:(RMMapView *)map;
- (void)beforeMapZoom:(RMMapView *)map byFactor:(float)zoomFactor near:(CGPoint)center;
- (void)afterMapZoom:(RMMapView *)map byFactor:(float)zoomFactor near:(CGPoint)center;
- (void)beforeMapZoom:(RMMapView *)map;
- (void)afterMapZoom:(RMMapView *)map;
/*
\brief Tells the delegate that the region displayed by the map view just changed.
... ... @@ -63,6 +62,4 @@
- (BOOL)mapView:(RMMapView *)map shouldDragAnnotation:(RMAnnotation *)annotation withEvent:(UIEvent *)event;
- (void)mapView:(RMMapView *)map didDragAnnotation:(RMAnnotation *)annotation withEvent:(UIEvent *)event;
- (void)afterMapTouch:(RMMapView *)map;
@end
... ...
... ... @@ -183,18 +183,4 @@
}
}
- (void)zoomByFactor:(float)zoomFactor near:(CGPoint)center
{
if (enableDragging) {
self.position = RMScaleCGPointAboutPoint(self.position, zoomFactor, center);
}
}
- (void)moveBy:(CGSize)delta
{
if (enableDragging) {
[super moveBy:delta];
}
}
@end
... ...
... ... @@ -27,6 +27,3 @@
static NSString* const RMSuspendNetworkOperations = @"RMSuspendNetworkOperations";
static NSString* const RMResumeNetworkOperations = @"RMResumeNetworkOperations";
static NSString* const RMMapImageRemovedFromScreenNotification = @"RMMapImageRemovedFromScreen";
static NSString* const RMMapImageLoadedNotification = @"RMMapImageLoaded";
static NSString* const RMMapImageLoadingCancelledNotification = @"RMMapImageLoadingCancelled";
... ...
... ... @@ -33,7 +33,7 @@
{
if (!(self = [super init]))
return nil;
// http://wiki.openstreetmap.org/index.php/FAQ#What_is_the_map_scale_for_a_particular_zoom_level_of_the_map.3F
[self setMaxZoom:18];
[self setMinZoom:1];
... ...
... ... @@ -392,18 +392,18 @@
}
}
- (void)moveBy:(CGSize)delta
{
if (enableDragging) {
[super moveBy:delta];
}
}
- (void)setPosition:(CGPoint)value
{
if (CGPointEqualToPoint(value, super.position) && CGRectEqualToRect(self.bounds, previousBounds)) return;
[self recalculateGeometry];
}
//- (void)moveBy:(CGSize)delta
//{
// if (enableDragging) {
// [super moveBy:delta];
// }
//}
//
//- (void)setPosition:(CGPoint)value
//{
// if (CGPointEqualToPoint(value, super.position) && CGRectEqualToRect(self.bounds, previousBounds)) return;
//
// [self recalculateGeometry];
//}
@end
... ...
... ... @@ -25,45 +25,12 @@
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#import <TargetConditionals.h>
#import <UIKit/UIKit.h>
#import "RMFoundation.h"
#import "RMNotifications.h"
#import "RMTile.h"
#import "FMDatabase.h"
@interface RMTileImage : NSObject {
// I know this is a bit nasty.
RMTile tile;
CGRect screenLocation;
// Only used when appropriate
CALayer *layer;
BOOL loadingCancelled;
}
@property (nonatomic, assign) CGRect screenLocation;
@property (nonatomic, readonly) RMTile tile;
@property (nonatomic, readonly) CALayer *layer;
@property (nonatomic, readonly) BOOL loadingCancelled;
@interface RMTileImage : NSObject
+ (UIImage *)errorTile;
+ (UIImage *)missingTile;
+ (RMTileImage *)tileImageWithTile:(RMTile)tile;
- (id)initWithTile:(RMTile)tile;
- (void)moveBy:(CGSize)delta;
- (void)zoomByFactor:(float)zoomFactor near:(CGPoint) center;
- (BOOL)isLoaded;
- (void)cancelLoading;
- (void)updateWithImage:(UIImage *)image andNotifyListeners:(BOOL)notifyListeners;
- (void)makeLayer;
+ (void)setErrorTile:(UIImage *)newErrorTile;
+ (void)setMissingTile:(UIImage *)newMissingTile;
@end
... ...
... ... @@ -25,13 +25,7 @@
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#import <QuartzCore/QuartzCore.h>
#import "RMGlobalConstants.h"
#import "RMTileImage.h"
#import "RMTileLoader.h"
#import "RMTileCache.h"
#import "RMPixel.h"
static BOOL _didLoadErrorTile = NO;
static BOOL _didLoadMissingTile = NO;
... ... @@ -40,10 +34,6 @@ static UIImage *_missingTile = nil;
@implementation RMTileImage
@synthesize screenLocation, tile, layer, loadingCancelled;
#pragma mark -
+ (UIImage *)errorTile
{
if (_errorTile)
... ... @@ -58,6 +48,14 @@ static UIImage *_missingTile = nil;
return _errorTile;
}
+ (void)setErrorTile:(UIImage *)newErrorTile
{
if (_errorTile == newErrorTile) return;
[_errorTile autorelease];
_errorTile = [newErrorTile retain];
_didLoadErrorTile = YES;
}
+ (UIImage *)missingTile
{
if (_missingTile)
... ... @@ -72,145 +70,12 @@ static UIImage *_missingTile = nil;
return _missingTile;
}
#pragma mark -
+ (RMTileImage *)tileImageWithTile:(RMTile)tile
+ (void)setMissingTile:(UIImage *)newMissingTile
{
return [[[RMTileImage alloc] initWithTile:tile] autorelease];
}
- (id)initWithTile:(RMTile)_tile
{
if (!(self = [super init]))
return nil;
tile = _tile;
layer = nil;
screenLocation = CGRectZero;
loadingCancelled = NO;
[self makeLayer];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(tileRemovedFromScreen:)
name:RMMapImageRemovedFromScreenNotification
object:self];
return self;
}
- (id)init
{
[NSException raise:@"Invalid initialiser" format:@"Use the designated initialiser for TileImage"];
[self release];
return nil;
}
- (void)tileRemovedFromScreen:(NSNotification *)notification
{
[self cancelLoading];
}
- (void)dealloc
{
// RMLog(@"Removing tile image %d %d %d", tile.x, tile.y, tile.zoom);
[[NSNotificationCenter defaultCenter] removeObserver:self];
[layer release]; layer = nil;
[super dealloc];
}
#pragma mark -
- (void)cancelLoading
{
loadingCancelled = YES;
[[NSNotificationCenter defaultCenter] postNotificationName:RMMapImageLoadingCancelledNotification object:self];
}
- (void)updateWithImage:(UIImage *)image andNotifyListeners:(BOOL)notifyListeners
{
dispatch_async(dispatch_get_main_queue(), ^{
layer.contents = (id)[image CGImage];
if (notifyListeners) {
[[NSNotificationCenter defaultCenter] postNotificationName:RMMapImageLoadedNotification object:self userInfo:nil];
}
});
}
- (BOOL)isLoaded
{
return (layer != nil && layer.contents != NULL);
}
- (NSUInteger)hash
{
return (NSUInteger)RMTileHash(tile);
}
- (BOOL)isEqual:(id)anObject
{
if (![anObject isKindOfClass:[RMTileImage class]])
return NO;
return RMTilesEqual(tile, [(RMTileImage *)anObject tile]);
}
- (void)makeLayer
{
if (layer == nil)
{
layer = [[CALayer alloc] init];
layer.contents = nil;
layer.anchorPoint = CGPointZero;
layer.bounds = CGRectMake(0, 0, screenLocation.size.width, screenLocation.size.height);
layer.position = screenLocation.origin;
layer.edgeAntialiasingMask = 0;
NSMutableDictionary *customActions = [NSMutableDictionary dictionaryWithDictionary:[layer actions]];
[customActions setObject:[NSNull null] forKey:@"position"];
[customActions setObject:[NSNull null] forKey:@"bounds"];
[customActions setObject:[NSNull null] forKey:kCAOnOrderOut];
[customActions setObject:[NSNull null] forKey:kCAOnOrderIn];
CATransition *reveal = [[CATransition alloc] init];
reveal.duration = 0.2;
reveal.type = kCATransitionFade;
[customActions setObject:reveal forKey:@"contents"];
[reveal release];
layer.actions = customActions;
}
}
- (void)moveBy:(CGSize)delta
{
self.screenLocation = RMTranslateCGRectBy(screenLocation, delta);
}
- (void)zoomByFactor:(float)zoomFactor near:(CGPoint)center
{
self.screenLocation = RMScaleCGRectAboutPoint(screenLocation, zoomFactor, center);
}
- (CGRect)screenLocation
{
return screenLocation;
}
- (void)setScreenLocation:(CGRect)newScreenLocation
{
// RMLog(@"location moving from %f %f to %f %f", screenLocation.origin.x, screenLocation.origin.y, newScreenLocation.origin.x, newScreenLocation.origin.y);
screenLocation = newScreenLocation;
if (layer != nil)
{
// layer.frame = screenLocation;
layer.position = screenLocation.origin;
layer.bounds = CGRectMake(0, 0, screenLocation.size.width, screenLocation.size.height);
}
if (_missingTile == newMissingTile) return;
[_missingTile autorelease];
_missingTile = [newMissingTile retain];
_didLoadMissingTile = YES;
}
@end
... ...
//
// RMTileImageSet.h
//
// Copyright (c) 2008-2009, Route-Me Contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#import <TargetConditionals.h>
#import <UIKit/UIKit.h>
#import "RMTile.h"
@class RMTileImage, RMTileCache;
@protocol RMTileSource;
@protocol RMTileImageSetDelegate <NSObject>
@optional
- (void)tileImageRemoved:(RMTileImage *)image;
- (void)tileImageAdded:(RMTileImage *)image;
@end
#pragma mark -
@interface RMTileImageSet : NSObject {
id <RMTileImageSetDelegate> delegate;
id <RMTileSource> tileSource;
RMTileCache *tileCache;
NSString *currentCacheKey;
NSMutableSet *images;
short zoom, tileDepth;
NSRecursiveLock *imagesLock;
}
@property (nonatomic, assign) id <RMTileImageSetDelegate> delegate;
// tileDepth defaults to zero. if tiles have no alpha, set this higher, 3 or so, to make zooming smoother
@property (nonatomic, assign) short tileDepth;
@property (nonatomic, assign) short zoom;
@property (nonatomic, readonly) BOOL fullyLoaded;
- (id)initWithDelegate:(id)delegate;
- (void)addTileImage:(RMTileImage *)image at:(CGRect)screenLocation;
- (void)addTile:(RMTile)tile at:(CGRect)screenLocation;
// Add tiles inside rect protected to bounds. Return rectangle containing bounds extended to full tile loading area
- (CGRect)loadTiles:(RMTileRect)rect toDisplayIn:(CGRect)bounds;
- (RMTileImage *)imageWithTile:(RMTile)tile;
- (void)removeTile:(RMTile)tile;
- (void)removeAllTiles;
- (void)resetTiles;
- (void)setTileSource:(id <RMTileSource>)newTileSource;
- (void)setTileCache:(RMTileCache *)newTileCache;
- (void)setCurrentCacheKey:(NSString *)newCacheKey;
- (NSUInteger)count;
- (void)moveBy:(CGSize)delta;
- (void)zoomByFactor:(float)zoomFactor near:(CGPoint)center;
- (void)printDebuggingInformation;
- (void)cancelLoading;
- (void)tileImageLoaded:(NSNotification *)notification;
- (void)removeTilesWorseThan:(RMTileImage *)newImage;
- (BOOL)isTile:(RMTile)subject worseThanTile:(RMTile)object;
- (RMTileImage *)anyTileImage;
- (void)removeTilesOutsideOf:(RMTileRect)rect;
@end
//
// RMTileImageSet.m
//
// Copyright (c) 2008-2009, Route-Me Contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#import "RMTileImageSet.h"
#import "RMTileImage.h"
#import "RMPixel.h"
#import "RMTileSource.h"
#import "RMTileCache.h"
// For notification strings
#import "RMTileLoader.h"
#import "RMMercatorToTileProjection.h"
@interface RMShuffleContainer : NSObject
{}
@property (nonatomic, assign) RMTile tile;
@property (nonatomic, assign) CGRect screenLocation;
+ (id)containerWithTile:(RMTile)aTile at:(CGRect)aScreenLocation;
- (id)initWithTile:(RMTile)aTile at:(CGRect)aScreenLocation;
@end
@implementation RMShuffleContainer
@synthesize tile, screenLocation;
+ (id)containerWithTile:(RMTile)aTile at:(CGRect)aScreenLocation
{
return [[[self alloc] initWithTile:aTile at:aScreenLocation] autorelease];
}
- (id)initWithTile:(RMTile)aTile at:(CGRect)aScreenLocation
{
if (!(self = [super init])) return nil;
self.tile = RMTileMake(aTile.x, aTile.y, aTile.zoom);
self.screenLocation = CGRectMake(aScreenLocation.origin.x, aScreenLocation.origin.y, aScreenLocation.size.width, aScreenLocation.size.height);
return self;
}
@end
#pragma mark -
@implementation RMTileImageSet
@synthesize delegate, tileDepth, zoom;
- (id)initWithDelegate:(id)aDelegate
{
if (!(self = [super init]))
return nil;
tileSource = nil;
tileCache = nil;
self.delegate = aDelegate;
self.tileDepth = 0;
images = [[NSMutableSet alloc] init];
imagesLock = [[NSRecursiveLock alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(tileImageLoaded:)
name:RMMapImageLoadedNotification
object:nil];
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self removeAllTiles];
[images release]; images = nil;
[imagesLock release]; imagesLock = nil;
[super dealloc];
}
- (void)removeTile:(RMTile)tile
{
NSAssert(!RMTileIsDummy(tile), @"attempted to remove dummy tile");
if (RMTileIsDummy(tile))
{
RMLog(@"attempted to remove dummy tile...??");
return;
}
[imagesLock lock];
RMTileImage *tileImage = [images member:[RMTileImage tileImageWithTile:tile]];
if (!tileImage) {
[imagesLock unlock];
return;
}
if ([delegate respondsToSelector:@selector(tileImageRemoved:)])
[delegate tileImageRemoved:tileImage];
[[NSNotificationCenter defaultCenter] postNotificationName:RMMapImageRemovedFromScreenNotification object:tileImage];
[tileImage cancelLoading];
[images removeObject:tileImage];
[imagesLock unlock];
}
- (void)removeAllTiles
{
[imagesLock lock];
for (NSInteger i=[images count]; i>0; --i)
{
RMTileImage *tileImage = [images anyObject];
if ([delegate respondsToSelector:@selector(tileImageRemoved:)])
[delegate tileImageRemoved:tileImage];
[[NSNotificationCenter defaultCenter] postNotificationName:RMMapImageRemovedFromScreenNotification object:tileImage];
[tileImage cancelLoading];
[images removeObject:tileImage];
}
[imagesLock unlock];
}
- (void)resetTiles
{
[imagesLock lock];
for (RMTileImage *tileImage in images)
{
[tileImage setScreenLocation:CGRectZero];
}
[imagesLock unlock];
}
- (void)setTileSource:(id <RMTileSource>)newTileSource
{
[self removeAllTiles];
tileSource = newTileSource;
}
- (void)setTileCache:(RMTileCache *)newTileCache
{
tileCache = newTileCache;
}
- (void)setCurrentCacheKey:(NSString *)newCacheKey
{
[currentCacheKey autorelease];
[newCacheKey retain];
currentCacheKey = newCacheKey;
}
- (void)addTileImage:(RMTileImage *)image at:(CGRect)screenLocation
{
BOOL tileNeeded;
tileNeeded = YES;
[imagesLock lock];
for (RMTileImage *tileImage in images)
{
if (![tileImage isLoaded])
continue;
if ([self isTile:image.tile worseThanTile:tileImage.tile])
{
tileNeeded = NO;
break;
}
}
[imagesLock unlock];
if (!tileNeeded)
return;
if ([image isLoaded])
[self removeTilesWorseThan:image];
image.screenLocation = screenLocation;
[imagesLock lock];
[images addObject:image];
[imagesLock unlock];
if (!RMTileIsDummy(image.tile)) {
if ([delegate respondsToSelector:@selector(tileImageAdded:)]) {
[delegate tileImageAdded:image];
}
}
}
- (void)addTile:(RMTile)tile at:(CGRect)screenLocation
{
// Is there an equivalent tile already in the cache?
RMTileImage *tileImage = nil;
[imagesLock lock];
tileImage = [[[images member:[RMTileImage tileImageWithTile:tile]] retain] autorelease];
[imagesLock unlock];
if (tileImage != nil) {
[tileImage setScreenLocation:screenLocation];
} else {
// Create empty RMTileImage
// Add the tile to the images on screen
tileImage = [RMTileImage tileImageWithTile:tile];
if (tileImage != nil)
[self addTileImage:tileImage at:screenLocation];
// In a queue:
// - check cache
// - check tilesource
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [tileCache cachedImage:tile withCacheKey:currentCacheKey];
if (image) {
[tileImage updateWithImage:image andNotifyListeners:NO];
[self removeTilesWorseThan:tileImage];
return;
}
// RMTileDump(tile);
// Return nil if you want to load the image asynchronously or display your own error tile (see [RMTileImage errorTile]
image = [tileSource imageForTileImage:tileImage addToCache:tileCache withCacheKey:currentCacheKey];
if (image) {
[tileImage updateWithImage:image andNotifyListeners:YES];
}
});
}
}
// Add tiles inside rect protected to bounds. Return rectangle containing bounds
// extended to full tile loading area
- (CGRect)loadTiles:(RMTileRect)rect toDisplayIn:(CGRect)bounds
{
RMTile t;
float pixelsPerTile = bounds.size.width / rect.size.width;
RMTileRect roundedRect = RMTileRectRound(rect);
// The number of tiles we'll load in the vertical and horizontal directions
int tileRegionWidth = (int)roundedRect.size.width;
int tileRegionHeight = (int)roundedRect.size.height;
id <RMMercatorToTileProjection> mercatorToTileProjection = [tileSource mercatorToTileProjection];
short minimumZoom = [tileSource minZoom], alternateMinimum;
// Now we translate the loaded region back into screen space for loadedBounds.
CGRect newLoadedBounds;
newLoadedBounds.origin.x = bounds.origin.x - (rect.origin.offset.x * pixelsPerTile);
newLoadedBounds.origin.y = bounds.origin.y - (rect.origin.offset.y * pixelsPerTile);
newLoadedBounds.size.width = tileRegionWidth * pixelsPerTile;
newLoadedBounds.size.height = tileRegionHeight * pixelsPerTile;
alternateMinimum = zoom - tileDepth - 1;
if (minimumZoom < alternateMinimum)
minimumZoom = alternateMinimum;
for (;;)
{
CGRect screenLocation;
screenLocation.size.width = pixelsPerTile;
screenLocation.size.height = pixelsPerTile;
t.zoom = rect.origin.tile.zoom;
NSMutableArray *tilesToLoad = [NSMutableArray array];
for (t.x = roundedRect.origin.tile.x; t.x < roundedRect.origin.tile.x + tileRegionWidth; t.x++)
{
for (t.y = roundedRect.origin.tile.y; t.y < roundedRect.origin.tile.y + tileRegionHeight; t.y++)
{
RMTile normalisedTile = [mercatorToTileProjection normaliseTile:t];
if (RMTileIsDummy(normalisedTile))
continue;
// this regrouping of terms is better for calculation precision (issue 128)
screenLocation.origin.x = bounds.origin.x + (t.x - rect.origin.tile.x - rect.origin.offset.x) * pixelsPerTile;
screenLocation.origin.y = bounds.origin.y + (t.y - rect.origin.tile.y - rect.origin.offset.y) * pixelsPerTile;
[tilesToLoad addObject:[RMShuffleContainer containerWithTile:normalisedTile at:screenLocation]];
}
}
// RMLog(@"%d tiles to load", [tilesToLoad count]);
// Load the tiles from the middle to the outside
for (NSInteger i=[tilesToLoad count]; i>0; --i)
{
NSInteger index = i/2;
RMShuffleContainer *tileToLoad = [tilesToLoad objectAtIndex:index];
[self addTile:tileToLoad.tile at:tileToLoad.screenLocation];
[tilesToLoad removeObjectAtIndex:index];
}
// adjust rect for next zoom level down until we're at minimum
if (--rect.origin.tile.zoom <= minimumZoom)
break;
if (rect.origin.tile.x & 1)
rect.origin.offset.x += 1.0;
if (rect.origin.tile.y & 1)
rect.origin.offset.y += 1.0;
rect.origin.tile.x /= 2;
rect.origin.tile.y /= 2;
rect.size.width *= 0.5;
rect.size.height *= 0.5;
rect.origin.offset.x *= 0.5;
rect.origin.offset.y *= 0.5;
pixelsPerTile = bounds.size.width / rect.size.width;
roundedRect = RMTileRectRound(rect);
// The number of tiles we'll load in the vertical and horizontal directions
tileRegionWidth = (int)roundedRect.size.width;
tileRegionHeight = (int)roundedRect.size.height;
}
return newLoadedBounds;
}
- (RMTileImage *)imageWithTile:(RMTile)tile
{
RMTileImage *tileImage = nil;
[imagesLock lock];
tileImage = [[[images member:[RMTileImage tileImageWithTile:tile]] retain] autorelease];
[imagesLock unlock];
return tileImage;
}
- (NSUInteger)count
{
return [images count];
}
- (void)moveBy:(CGSize)delta
{
[imagesLock lock];
for (RMTileImage *image in images)
{
[image moveBy:delta];
}
[imagesLock unlock];
}
- (void)zoomByFactor:(float)zoomFactor near:(CGPoint)center
{
[imagesLock lock];
for (RMTileImage *image in images)
{
[image zoomByFactor:zoomFactor near:center];
}
[imagesLock unlock];
}
- (void)printDebuggingInformation
{
float biggestSeamRight = 0.0f;
float biggestSeamDown = 0.0f;
[imagesLock lock];
for (RMTileImage *image in images)
{
CGRect location = [image screenLocation];
/* RMLog(@"Image at %f, %f %f %f",
location.origin.x,
location.origin.y,
location.origin.x + location.size.width,
location.origin.y + location.size.height);
*/
float seamRight = INFINITY;
float seamDown = INFINITY;
for (RMTileImage *other_image in images)
{
CGRect other_location = [other_image screenLocation];
if (other_location.origin.x > location.origin.x)
seamRight = MIN(seamRight, other_location.origin.x - (location.origin.x + location.size.width));
if (other_location.origin.y > location.origin.y)
seamDown = MIN(seamDown, other_location.origin.y - (location.origin.y + location.size.height));
}
if (seamRight != INFINITY)
biggestSeamRight = MAX(biggestSeamRight, seamRight);
if (seamDown != INFINITY)
biggestSeamDown = MAX(biggestSeamDown, seamDown);
}
[imagesLock unlock];
RMLog(@"Biggest seam right: %f down: %f", biggestSeamRight, biggestSeamDown);
}
- (void)cancelLoading
{
[imagesLock lock];
for (RMTileImage *image in images)
{
[image cancelLoading];
}
[imagesLock unlock];
}
- (RMTileImage *)anyTileImage
{
RMTileImage *tileImage = nil;
[imagesLock lock];
tileImage = [[[images anyObject] retain] autorelease];
[imagesLock unlock];
return tileImage;
}
- (short)zoom
{
return zoom;
}
- (void)setZoom:(short)value
{
if (zoom == value) {
// no need to act
return;
}
zoom = value;
if (tileDepth == 0) {
[self removeAllTiles];
return;
}
[imagesLock lock];
for (RMTileImage *image in [images allObjects])
{
if (![image isLoaded]) {
continue;
}
[self removeTilesWorseThan:image];
}
[imagesLock unlock];
}
- (BOOL)fullyLoaded
{
BOOL fullyLoaded = YES;
[imagesLock lock];
for (RMTileImage *image in images)
{
if (![image isLoaded])
{
fullyLoaded = NO;
break;
}
}
[imagesLock unlock];
return fullyLoaded;
}
- (void)tileImageLoaded:(NSNotification *)notification
{
RMTileImage *img = (RMTileImage *)[notification object];
BOOL removeTiles = NO;
[imagesLock lock];
if (img && img == [images member:img]) {
removeTiles = YES;
}
[imagesLock unlock];
if (removeTiles) [self removeTilesWorseThan:img];
}
- (void)removeTilesWorseThan:(RMTileImage *)newImage
{
RMTile newTile = newImage.tile;
if (newTile.zoom > zoom) {
// no tiles are worse since this one is too detailed to keep long-term
return;
}
[imagesLock lock];
for (RMTileImage *oldImage in [images allObjects])
{
RMTile oldTile = oldImage.tile;
if (oldImage == newImage)
continue;
if ([self isTile:oldTile worseThanTile:newTile]) {
[oldImage cancelLoading];
[self removeTile:oldTile];
}
}
[imagesLock unlock];
}
- (BOOL)isTile:(RMTile)subject worseThanTile:(RMTile)object
{
short subjZ, objZ;
uint32_t sx, sy, ox, oy;
objZ = object.zoom;
if (objZ > zoom) {
// can't be worse than this tile, it's too detailed to keep long-term
return NO;
}
subjZ = subject.zoom;
if (subjZ + tileDepth >= zoom && subjZ <= zoom)
{
// this tile isn't bad, it's within zoom limits
return NO;
}
sx = subject.x;
sy = subject.y;
ox = object.x;
oy = object.y;
if (subjZ < objZ) {
// old tile is larger & blurrier
unsigned int dz = objZ - subjZ;
ox >>= dz;
oy >>= dz;
}
else if (objZ < subjZ) {
// old tile is smaller & more detailed
unsigned int dz = subjZ - objZ;
sx >>= dz;
sy >>= dz;
}
if (sx != ox || sy != oy) {
// Tiles don't overlap
return NO;
}
if (abs(zoom - subjZ) < abs(zoom - objZ)) {
// subject is closer to desired zoom level than object, so it's not worse
return NO;
}
return YES;
}
- (void)removeTilesOutsideOf:(RMTileRect)rect
{
uint32_t minX, maxX, minY, maxY, span;
short currentZoom = rect.origin.tile.zoom;
RMTile wrappedTile;
id <RMMercatorToTileProjection> mercatorToTileProjection = [tileSource mercatorToTileProjection];
rect = RMTileRectRound(rect);
minX = rect.origin.tile.x;
span = rect.size.width > 1.0f ? (uint32_t)rect.size.width - 1 : 0;
maxX = rect.origin.tile.x + span;
minY = rect.origin.tile.y;
span = rect.size.height > 1.0f ? (uint32_t)rect.size.height - 1 : 0;
maxY = rect.origin.tile.y + span;
wrappedTile.x = maxX;
wrappedTile.y = maxY;
wrappedTile.zoom = rect.origin.tile.zoom;
wrappedTile = [mercatorToTileProjection normaliseTile:wrappedTile];
if (!RMTileIsDummy(wrappedTile))
maxX = wrappedTile.x;
[imagesLock lock];
for (RMTileImage *img in [images allObjects])
{
RMTile tile = img.tile;
short tileZoom = tile.zoom;
uint32_t x, y, zoomedMinX, zoomedMaxX, zoomedMinY, zoomedMaxY;
x = tile.x;
y = tile.y;
zoomedMinX = minX;
zoomedMaxX = maxX;
zoomedMinY = minY;
zoomedMaxY = maxY;
if (tileZoom < currentZoom)
{
// Tile is too large for current zoom level
unsigned int dz = currentZoom - tileZoom;
zoomedMinX >>= dz;
zoomedMaxX >>= dz;
zoomedMinY >>= dz;
zoomedMaxY >>= dz;
}
else
{
// Tile is too small & detailed for current zoom level
unsigned int dz = tileZoom - currentZoom;
x >>= dz;
y >>= dz;
}
if (y >= zoomedMinY && y <= zoomedMaxY)
{
if (zoomedMinX <= zoomedMaxX)
{
if (x >= zoomedMinX && x <= zoomedMaxX)
continue;
}
else
{
if (x >= zoomedMinX || x <= zoomedMaxX)
continue;
}
}
// if haven't continued, tile is outside of rect
[self removeTile:tile];
}
[imagesLock unlock];
}
@end
//
// RMTimeImageSet.h
//
// Copyright (c) 2008-2009, Route-Me Contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/Foundation.h>
#import "RMTile.h"
@class RMTileImage;
@class RMTileImageSet;
@class RMMercatorToScreenProjection;
@class RMMapView;
@interface RMTileLoader : NSObject {
RMMapView *mapView;
CGRect loadedBounds;
NSUInteger loadedZoom;
RMTileRect loadedTiles;
BOOL suppressLoading;
}
@property (nonatomic, readonly) CGRect loadedBounds;
@property (nonatomic, readonly) NSUInteger loadedZoom;
@property (nonatomic, assign) BOOL suppressLoading;
/// Designated initialiser
- (id)initWithView:(RMMapView *)aMapView;
- (void)updateLoadedImages;
- (void)moveBy:(CGSize)delta;
- (void)zoomByFactor:(float)zoomFactor near:(CGPoint)center;
- (void)clearLoadedBounds;
- (void)reload;
- (void)reset;
@end
//
// RMTimeImageSet.m
//
// Copyright (c) 2008-2009, Route-Me Contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#import "RMGlobalConstants.h"
#import "RMTileLoader.h"
#import "RMMapView.h"
#import "RMTileImage.h"
#import "RMTileSource.h"
#import "RMPixel.h"
#import "RMMercatorToScreenProjection.h"
#import "RMFractalTileProjection.h"
#import "RMTileImageSet.h"
#import "RMTileCache.h"
@implementation RMTileLoader
@synthesize loadedBounds, loadedZoom;
- (id)init
{
if (!(self = [self initWithView:nil]))
return nil;
return self;
}
- (id)initWithView:(RMMapView *)aMapView
{
if (!(self = [super init]))
return nil;
mapView = aMapView;
[self clearLoadedBounds];
loadedTiles.origin.tile = RMTileDummy();
suppressLoading = NO;
return self;
}
- (void)clearLoadedBounds
{
loadedBounds = CGRectZero;
[[mapView imagesOnScreen] resetTiles];
}
- (BOOL)isScreenLoaded
{
// RMTileRect targetRect = [content tileBounds];
BOOL contained = CGRectContainsRect(loadedBounds, [mapView screenBounds]);
NSUInteger targetZoom = (NSUInteger)([[mapView mercatorToTileProjection] calculateNormalisedZoomFromScale:[mapView scaledMetersPerPixel]]);
if ((targetZoom > mapView.maxZoom) || (targetZoom < mapView.minZoom))
{
RMLog(@"target zoom %d is outside of RMMapContents limits %f to %f", targetZoom, mapView.minZoom, mapView.maxZoom);
}
// if (contained == NO)
// {
// RMLog(@"reassembling because its not contained");
// }
// if (targetZoom != loadedZoom)
// {
// RMLog(@"reassembling because target zoom = %f, loaded zoom = %d", targetZoom, loadedZoom);
// }
return contained && targetZoom == loadedZoom;
}
- (void)updateLoadedImages
{
if (suppressLoading)
return;
if ([mapView mercatorToTileProjection] == nil || [mapView mercatorToScreenProjection] == nil)
return;
if ([self isScreenLoaded])
return;
RMTileRect newTileRect = [mapView tileBounds];
RMTileImageSet *images = [mapView imagesOnScreen];
images.zoom = newTileRect.origin.tile.zoom;
CGRect newLoadedBounds = [images loadTiles:newTileRect toDisplayIn:[mapView screenBounds]];
if (!RMTileIsDummy(loadedTiles.origin.tile))
{
[images removeTilesOutsideOf:newTileRect];
}
loadedBounds = newLoadedBounds;
loadedZoom = newTileRect.origin.tile.zoom;
loadedTiles = newTileRect;
}
- (void)moveBy:(CGSize)delta
{
loadedBounds = RMTranslateCGRectBy(loadedBounds, delta);
[self updateLoadedImages];
}
- (void)zoomByFactor:(float)zoomFactor near:(CGPoint)center
{
loadedBounds = RMScaleCGRectAboutPoint(loadedBounds, zoomFactor, center);
[self updateLoadedImages];
}
- (BOOL)suppressLoading
{
return suppressLoading;
}
- (void)setSuppressLoading:(BOOL)suppress
{
suppressLoading = suppress;
if (suppress == NO)
[self updateLoadedImages];
}
- (void)reset
{
loadedTiles.origin.tile = RMTileDummy();
}
- (void)reload
{
[self clearLoadedBounds];
[self updateLoadedImages];
}
@end
... ... @@ -40,7 +40,7 @@
@protocol RMTileSource <NSObject>
- (UIImage *)imageForTileImage:(RMTileImage *)tileImage addToCache:(RMTileCache *)tileCache withCacheKey:(NSString *)aCacheKey;
- (UIImage *)imageForTile:(RMTile)tile inCache:(RMTileCache *)tileCache;
- (id <RMMercatorToTileProjection>)mercatorToTileProjection;
- (RMProjection *)projection;
... ...