...
|
...
|
@@ -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 |
...
|
...
|
|