/MapView/Map/RMMapContents.m

http://github.com/route-me/route-me · Objective C · 1120 lines · 817 code · 214 blank · 89 comment · 88 complexity · 8d1de5303be5f1482eacc42332b7b3a2 MD5 · raw file

  1. //
  2. // RMMapContents.m
  3. //
  4. // Copyright (c) 2008-2009, Route-Me Contributors
  5. // All rights reserved.
  6. //
  7. // Redistribution and use in source and binary forms, with or without
  8. // modification, are permitted provided that the following conditions are met:
  9. //
  10. // * Redistributions of source code must retain the above copyright notice, this
  11. // list of conditions and the following disclaimer.
  12. // * Redistributions in binary form must reproduce the above copyright notice,
  13. // this list of conditions and the following disclaimer in the documentation
  14. // and/or other materials provided with the distribution.
  15. //
  16. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  17. // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  18. // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  19. // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  20. // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  21. // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  22. // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  23. // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  24. // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  25. // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  26. // POSSIBILITY OF SUCH DAMAGE.
  27. #import "RMGlobalConstants.h"
  28. #import "RMMapContents.h"
  29. #import "RMMapView.h"
  30. #import "RMFoundation.h"
  31. #import "RMProjection.h"
  32. #import "RMMercatorToScreenProjection.h"
  33. #import "RMMercatorToTileProjection.h"
  34. #import "RMTileSource.h"
  35. #import "RMTileLoader.h"
  36. #import "RMTileImageSet.h"
  37. #import "RMOpenStreetMapSource.h"
  38. #import "RMCoreAnimationRenderer.h"
  39. #import "RMCachedTileSource.h"
  40. #import "RMLayerCollection.h"
  41. #import "RMMarkerManager.h"
  42. #import "RMMarker.h"
  43. @interface RMMapContents (PrivateMethods)
  44. - (void)animatedZoomStep:(NSTimer *)timer;
  45. @end
  46. @implementation RMMapContents (Internal)
  47. BOOL delegateHasRegionUpdate;
  48. @end
  49. @implementation RMMapContents
  50. @synthesize boundingMask;
  51. @synthesize minZoom;
  52. @synthesize maxZoom;
  53. @synthesize screenScale;
  54. @synthesize markerManager;
  55. #pragma mark --- begin constants ----
  56. #define kZoomAnimationStepTime 0.03f
  57. #define kZoomAnimationAnimationTime 0.1f
  58. #define kiPhoneMilimeteresPerPixel .1543
  59. #define kZoomRectPixelBuffer 50
  60. #pragma mark --- end constants ----
  61. #pragma mark Initialisation
  62. - (id)initWithView: (UIView*) view
  63. {
  64. LogMethod();
  65. CLLocationCoordinate2D here;
  66. here.latitude = kDefaultInitialLatitude;
  67. here.longitude = kDefaultInitialLongitude;
  68. return [self initWithView:view
  69. tilesource:[[RMOpenStreetMapSource alloc] init]
  70. centerLatLon:here
  71. zoomLevel:kDefaultInitialZoomLevel
  72. maxZoomLevel:kDefaultMaximumZoomLevel
  73. minZoomLevel:kDefaultMinimumZoomLevel
  74. backgroundImage:nil
  75. screenScale:0];
  76. }
  77. - (id)initWithView: (UIView*) view screenScale:(float)theScreenScale {
  78. LogMethod();
  79. CLLocationCoordinate2D here;
  80. here.latitude = kDefaultInitialLatitude;
  81. here.longitude = kDefaultInitialLongitude;
  82. return [self initWithView:view
  83. tilesource:[[RMOpenStreetMapSource alloc] init]
  84. centerLatLon:here
  85. zoomLevel:kDefaultInitialZoomLevel
  86. maxZoomLevel:kDefaultMaximumZoomLevel
  87. minZoomLevel:kDefaultMinimumZoomLevel
  88. backgroundImage:nil
  89. screenScale:theScreenScale];
  90. }
  91. - (id)initWithView: (UIView*) view
  92. tilesource:(id<RMTileSource>)newTilesource
  93. {
  94. return [self initWithView:view tilesource:newTilesource screenScale:0.0];
  95. }
  96. -(id)initWithView:(UIView *)view tilesource:(id<RMTileSource>)newTilesource screenScale:(float)theScreenScale
  97. {
  98. LogMethod();
  99. CLLocationCoordinate2D here;
  100. here.latitude = kDefaultInitialLatitude;
  101. here.longitude = kDefaultInitialLongitude;
  102. return [self initWithView:view
  103. tilesource:newTilesource
  104. centerLatLon:here
  105. zoomLevel:kDefaultInitialZoomLevel
  106. maxZoomLevel:kDefaultMaximumZoomLevel
  107. minZoomLevel:kDefaultMinimumZoomLevel
  108. backgroundImage:nil
  109. screenScale:theScreenScale];
  110. }
  111. - (id)initWithView:(UIView*)newView
  112. tilesource:(id<RMTileSource>)newTilesource
  113. centerLatLon:(CLLocationCoordinate2D)initialCenter
  114. zoomLevel:(float)initialZoomLevel
  115. maxZoomLevel:(float)maxZoomLevel
  116. minZoomLevel:(float)minZoomLevel
  117. backgroundImage:(UIImage *)backgroundImage
  118. screenScale:(float)theScreenScale
  119. {
  120. LogMethod();
  121. if (![super init])
  122. return nil;
  123. NSAssert1([newView isKindOfClass:[RMMapView class]], @"view %@ must be a subclass of RMMapView", newView);
  124. [(RMMapView *)newView setContents:self];
  125. tileSource = nil;
  126. projection = nil;
  127. mercatorToTileProjection = nil;
  128. renderer = nil;
  129. imagesOnScreen = nil;
  130. tileLoader = nil;
  131. screenScale = (theScreenScale == 0.0 ? 1.0 : theScreenScale);
  132. boundingMask = RMMapMinWidthBound;
  133. mercatorToScreenProjection = [[RMMercatorToScreenProjection alloc] initFromProjection:[newTilesource projection] ToScreenBounds:[newView bounds]];
  134. layer = [[newView layer] retain];
  135. [self setMinZoom:minZoomLevel];
  136. [self setMaxZoom:maxZoomLevel];
  137. [self setTileSource:newTilesource];
  138. [self setRenderer: [[[RMCoreAnimationRenderer alloc] initWithContent:self] autorelease]];
  139. imagesOnScreen = [[RMTileImageSet alloc] initWithDelegate:renderer];
  140. [imagesOnScreen setTileSource:tileSource];
  141. tileLoader = [[RMTileLoader alloc] initWithContent:self];
  142. [tileLoader setSuppressLoading:YES];
  143. [self setZoom:initialZoomLevel];
  144. [self moveToLatLong:initialCenter];
  145. [tileLoader setSuppressLoading:NO];
  146. /// \bug TODO: Make a nice background class
  147. RMMapLayer *theBackground = [[RMMapLayer alloc] init];
  148. [self setBackground:theBackground];
  149. [theBackground release];
  150. RMLayerCollection *theOverlay = [[RMLayerCollection alloc] initForContents:self];
  151. [self setOverlay:theOverlay];
  152. [theOverlay release];
  153. markerManager = [[RMMarkerManager alloc] initWithContents:self];
  154. [newView setNeedsDisplay];
  155. [[NSNotificationCenter defaultCenter] addObserver:self
  156. selector:@selector(handleMemoryWarningNotification:)
  157. name:UIApplicationDidReceiveMemoryWarningNotification
  158. object:nil];
  159. RMLog(@"Map contents initialised. view: %@ tileSource %@ renderer %@", newView, tileSource, renderer);
  160. return self;
  161. }
  162. /// deprecated at any moment after release 0.5
  163. - (id) initForView: (UIView*) view
  164. {
  165. WarnDeprecated();
  166. return [self initWithView:view];
  167. }
  168. /// deprecated at any moment after release 0.5
  169. - (id) initForView: (UIView*) view WithLocation:(CLLocationCoordinate2D)latlong
  170. {
  171. WarnDeprecated();
  172. LogMethod();
  173. id<RMTileSource> _tileSource = [[RMOpenStreetMapSource alloc] init];
  174. RMMapRenderer *_renderer = [[RMCoreAnimationRenderer alloc] initWithContent:self];
  175. id mapContents = [self initForView:view WithTileSource:_tileSource WithRenderer:_renderer LookingAt:latlong];
  176. [_tileSource release];
  177. [_renderer release];
  178. return mapContents;
  179. }
  180. /// deprecated at any moment after release 0.5
  181. - (id) initForView: (UIView*) view WithTileSource: (id<RMTileSource>)_tileSource WithRenderer: (RMMapRenderer*)_renderer LookingAt:(CLLocationCoordinate2D)latlong
  182. {
  183. WarnDeprecated();
  184. LogMethod();
  185. if (![super init])
  186. return nil;
  187. NSAssert1([view isKindOfClass:[RMMapView class]], @"view %@ must be a subclass of RMMapView", view);
  188. self.boundingMask = RMMapMinWidthBound;
  189. // targetView = view;
  190. mercatorToScreenProjection = [[RMMercatorToScreenProjection alloc] initFromProjection:[_tileSource projection] ToScreenBounds:[view bounds]];
  191. tileSource = nil;
  192. projection = nil;
  193. mercatorToTileProjection = nil;
  194. renderer = nil;
  195. imagesOnScreen = nil;
  196. tileLoader = nil;
  197. layer = [[view layer] retain];
  198. [self setTileSource:_tileSource];
  199. [self setRenderer:_renderer];
  200. imagesOnScreen = [[RMTileImageSet alloc] initWithDelegate:renderer];
  201. [imagesOnScreen setTileSource:tileSource];
  202. tileLoader = [[RMTileLoader alloc] initWithContent:self];
  203. [tileLoader setSuppressLoading:YES];
  204. [self setMinZoom:kDefaultMinimumZoomLevel];
  205. [self setMaxZoom:kDefaultMaximumZoomLevel];
  206. [self setZoom:kDefaultInitialZoomLevel];
  207. [self moveToLatLong:latlong];
  208. [tileLoader setSuppressLoading:NO];
  209. /// \bug TODO: Make a nice background class
  210. RMMapLayer *theBackground = [[RMMapLayer alloc] init];
  211. [self setBackground:theBackground];
  212. [theBackground release];
  213. RMLayerCollection *theOverlay = [[RMLayerCollection alloc] initForContents:self];
  214. [self setOverlay:theOverlay];
  215. [theOverlay release];
  216. markerManager = [[RMMarkerManager alloc] initWithContents:self];
  217. [view setNeedsDisplay];
  218. [[NSNotificationCenter defaultCenter] addObserver:self
  219. selector:@selector(handleMemoryWarningNotification:)
  220. name:UIApplicationDidReceiveMemoryWarningNotification
  221. object:nil];
  222. RMLog(@"Map contents initialised. view: %@ tileSource %@ renderer %@", view, tileSource, renderer);
  223. return self;
  224. }
  225. - (void)setFrame:(CGRect)frame
  226. {
  227. CGRect bounds = CGRectMake(0, 0, frame.size.width, frame.size.height);
  228. [mercatorToScreenProjection setScreenBounds:bounds];
  229. background.frame = bounds;
  230. layer.frame = frame;
  231. overlay.frame = bounds;
  232. [tileLoader clearLoadedBounds];
  233. [tileLoader updateLoadedImages];
  234. [renderer setFrame:frame];
  235. [overlay correctPositionOfAllSublayers];
  236. }
  237. -(void) dealloc
  238. {
  239. LogMethod();
  240. [[NSNotificationCenter defaultCenter] removeObserver:self];
  241. [imagesOnScreen cancelLoading];
  242. [self setRenderer:nil];
  243. [imagesOnScreen release];
  244. [tileLoader release];
  245. [projection release];
  246. [mercatorToTileProjection release];
  247. [mercatorToScreenProjection release];
  248. [tileSource release];
  249. [self setOverlay:nil];
  250. [self setBackground:nil];
  251. [layer release];
  252. [markerManager release];
  253. [super dealloc];
  254. }
  255. - (void)handleMemoryWarningNotification:(NSNotification *)notification
  256. {
  257. [self didReceiveMemoryWarning];
  258. }
  259. - (void) didReceiveMemoryWarning
  260. {
  261. LogMethod();
  262. [tileSource didReceiveMemoryWarning];
  263. }
  264. #pragma mark Forwarded Events
  265. - (void)moveToLatLong: (CLLocationCoordinate2D)latlong
  266. {
  267. RMProjectedPoint aPoint = [[self projection] latLongToPoint:latlong];
  268. [self moveToProjectedPoint: aPoint];
  269. }
  270. - (void)moveToProjectedPoint: (RMProjectedPoint)aPoint
  271. {
  272. self.centerProjectedPoint = aPoint;
  273. }
  274. - (void)moveBy: (CGSize) delta
  275. {
  276. [mercatorToScreenProjection moveScreenBy:delta];
  277. [imagesOnScreen moveBy:delta];
  278. [tileLoader moveBy:delta];
  279. [overlay moveBy:delta];
  280. [overlay correctPositionOfAllSublayers];
  281. [renderer setNeedsDisplay];
  282. }
  283. /// \bug doesn't really adjust anything, just makes a computation. CLANG flags some dead assignments (write-only variables)
  284. - (float)adjustZoomForBoundingMask:(float)zoomFactor
  285. {
  286. if ( boundingMask == RMMapNoMinBound )
  287. return zoomFactor;
  288. double newMPP = self.metersPerPixel / zoomFactor;
  289. RMProjectedRect mercatorBounds = [[tileSource projection] planetBounds];
  290. // Check for MinWidthBound
  291. if ( boundingMask & RMMapMinWidthBound )
  292. {
  293. double newMapContentsWidth = mercatorBounds.size.width / newMPP;
  294. double screenBoundsWidth = [self screenBounds].size.width;
  295. double mapContentWidth;
  296. if ( newMapContentsWidth < screenBoundsWidth )
  297. {
  298. // Calculate new zoom facter so that it does not shrink the map any further.
  299. mapContentWidth = mercatorBounds.size.width / self.metersPerPixel;
  300. zoomFactor = screenBoundsWidth / mapContentWidth;
  301. //newMPP = self.metersPerPixel / zoomFactor;
  302. //newMapContentsWidth = mercatorBounds.size.width / newMPP;
  303. }
  304. }
  305. // Check for MinHeightBound
  306. if ( boundingMask & RMMapMinHeightBound )
  307. {
  308. double newMapContentsHeight = mercatorBounds.size.height / newMPP;
  309. double screenBoundsHeight = [self screenBounds].size.height;
  310. double mapContentHeight;
  311. if ( newMapContentsHeight < screenBoundsHeight )
  312. {
  313. // Calculate new zoom facter so that it does not shrink the map any further.
  314. mapContentHeight = mercatorBounds.size.height / self.metersPerPixel;
  315. zoomFactor = screenBoundsHeight / mapContentHeight;
  316. //newMPP = self.metersPerPixel / zoomFactor;
  317. //newMapContentsHeight = mercatorBounds.size.height / newMPP;
  318. }
  319. }
  320. //[self adjustMapPlacementWithScale:newMPP];
  321. return zoomFactor;
  322. }
  323. /// This currently is not called because it does not handle the case when the map is continous or not continous. At a certain scale
  324. /// you can continuously move to the west or east until you get to a certain scale level that simply shows the entire world.
  325. - (void)adjustMapPlacementWithScale:(float)aScale
  326. {
  327. CGSize adjustmentDelta = {0.0, 0.0};
  328. RMLatLong rightEdgeLatLong = {0, kMaxLong};
  329. RMLatLong leftEdgeLatLong = {0,- kMaxLong};
  330. CGPoint rightEdge = [self latLongToPixel:rightEdgeLatLong withMetersPerPixel:aScale];
  331. CGPoint leftEdge = [self latLongToPixel:leftEdgeLatLong withMetersPerPixel:aScale];
  332. //CGPoint topEdge = [self latLongToPixel:myLatLong withMetersPerPixel:aScale];
  333. //CGPoint bottomEdge = [self latLongToPixel:myLatLong withMetersPerPixel:aScale];
  334. CGRect containerBounds = [self screenBounds];
  335. if ( rightEdge.x < containerBounds.size.width )
  336. {
  337. adjustmentDelta.width = containerBounds.size.width - rightEdge.x;
  338. [self moveBy:adjustmentDelta];
  339. }
  340. if ( leftEdge.x > containerBounds.origin.x )
  341. {
  342. adjustmentDelta.width = containerBounds.origin.x - leftEdge.x;
  343. [self moveBy:adjustmentDelta];
  344. }
  345. }
  346. /// \bug this is a no-op, not a clamp, if new zoom would be outside of minzoom/maxzoom range
  347. - (void)zoomByFactor: (float) zoomFactor near:(CGPoint) pivot
  348. {
  349. //[self zoomByFactor:zoomFactor near:pivot animated:NO];
  350. zoomFactor = [self adjustZoomForBoundingMask:zoomFactor];
  351. //RMLog(@"Zoom Factor: %lf for Zoom:%f", zoomFactor, [self zoom]);
  352. // pre-calculate zoom so we can tell if we want to perform it
  353. float newZoom = [mercatorToTileProjection
  354. calculateZoomFromScale:self.metersPerPixel/zoomFactor];
  355. if ((newZoom > minZoom) && (newZoom < maxZoom))
  356. {
  357. [mercatorToScreenProjection zoomScreenByFactor:zoomFactor near:pivot];
  358. [imagesOnScreen zoomByFactor:zoomFactor near:pivot];
  359. [tileLoader zoomByFactor:zoomFactor near:pivot];
  360. [overlay zoomByFactor:zoomFactor near:pivot];
  361. [overlay correctPositionOfAllSublayers];
  362. [renderer setNeedsDisplay];
  363. }
  364. }
  365. - (void)zoomByFactor: (float) zoomFactor near:(CGPoint) pivot animated:(BOOL) animated
  366. {
  367. [self zoomByFactor:zoomFactor near:pivot animated:animated withCallback:nil];
  368. }
  369. - (BOOL)shouldZoomToTargetZoom:(float)targetZoom withZoomFactor:(float)zoomFactor {
  370. //bools for syntactical sugar to understand the logic in the if statement below
  371. BOOL zoomAtMax = ([self zoom] == [self maxZoom]);
  372. BOOL zoomAtMin = ([self zoom] == [self minZoom]);
  373. BOOL zoomGreaterMin = ([self zoom] > [self minZoom]);
  374. BOOL zoomLessMax = ([self zoom] < [self maxZoom]);
  375. //zooming in zoomFactor > 1
  376. //zooming out zoomFactor < 1
  377. if ((zoomGreaterMin && zoomLessMax) || (zoomAtMax && zoomFactor<1) || (zoomAtMin && zoomFactor>1))
  378. {
  379. return YES;
  380. }
  381. else
  382. {
  383. return NO;
  384. }
  385. }
  386. - (void)zoomByFactor: (float) zoomFactor near:(CGPoint) pivot animated:(BOOL) animated withCallback:(id<RMMapContentsAnimationCallback>)callback
  387. {
  388. zoomFactor = [self adjustZoomForBoundingMask:zoomFactor];
  389. float zoomDelta = log2f(zoomFactor);
  390. float targetZoom = zoomDelta + [self zoom];
  391. if (targetZoom == [self zoom]){
  392. return;
  393. }
  394. // clamp zoom to remain below or equal to maxZoom after zoomAfter will be applied
  395. // Set targetZoom to maxZoom so the map zooms to its maximum
  396. if(targetZoom > [self maxZoom]){
  397. zoomFactor = exp2f([self maxZoom] - [self zoom]);
  398. targetZoom = [self maxZoom];
  399. }
  400. // clamp zoom to remain above or equal to minZoom after zoomAfter will be applied
  401. // Set targetZoom to minZoom so the map zooms to its maximum
  402. if(targetZoom < [self minZoom]){
  403. zoomFactor = 1/exp2f([self zoom] - [self minZoom]);
  404. targetZoom = [self minZoom];
  405. }
  406. if ([self shouldZoomToTargetZoom:targetZoom withZoomFactor:zoomFactor])
  407. {
  408. if (animated)
  409. {
  410. // goal is to complete the animation in animTime seconds
  411. double nSteps = round(kZoomAnimationAnimationTime / kZoomAnimationStepTime);
  412. double zoomIncr = zoomDelta / nSteps;
  413. NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
  414. [NSNumber numberWithDouble:zoomIncr], @"zoomIncr",
  415. [NSNumber numberWithDouble:targetZoom], @"targetZoom",
  416. [NSValue valueWithCGPoint:pivot], @"pivot",
  417. [NSNumber numberWithFloat:zoomFactor], @"factor",
  418. callback, @"callback", nil];
  419. [NSTimer scheduledTimerWithTimeInterval:kZoomAnimationStepTime
  420. target:self
  421. selector:@selector(animatedZoomStep:)
  422. userInfo:userInfo
  423. repeats:YES];
  424. }
  425. else
  426. {
  427. [mercatorToScreenProjection zoomScreenByFactor:zoomFactor near:pivot];
  428. [imagesOnScreen zoomByFactor:zoomFactor near:pivot];
  429. [tileLoader zoomByFactor:zoomFactor near:pivot];
  430. [overlay zoomByFactor:zoomFactor near:pivot];
  431. [overlay correctPositionOfAllSublayers];
  432. [renderer setNeedsDisplay];
  433. }
  434. }
  435. else
  436. {
  437. if([self zoom] > [self maxZoom])
  438. [self setZoom:[self maxZoom]];
  439. if([self zoom] < [self minZoom])
  440. [self setZoom:[self minZoom]];
  441. }
  442. }
  443. /// \bug magic strings embedded in code
  444. - (void)animatedZoomStep:(NSTimer *)timer
  445. {
  446. double zoomIncr = [[[timer userInfo] objectForKey:@"zoomIncr"] doubleValue];
  447. double targetZoom = [[[timer userInfo] objectForKey:@"targetZoom"] doubleValue];
  448. NSDictionary *userInfo = [[[timer userInfo] retain] autorelease];
  449. id<RMMapContentsAnimationCallback> callback = [userInfo objectForKey:@"callback"];
  450. if ((zoomIncr > 0 && [self zoom] >= targetZoom-1.0e-6) || (zoomIncr < 0 && [self zoom] <= targetZoom+1.0e-6))
  451. {
  452. if ( [self zoom] != targetZoom ) [self setZoom:targetZoom];
  453. [timer invalidate]; // ASAP
  454. if ([callback respondsToSelector:@selector(animationFinishedWithZoomFactor:near:)])
  455. {
  456. [callback animationFinishedWithZoomFactor:[[userInfo objectForKey:@"factor"] floatValue] near:[[userInfo objectForKey:@"pivot"] CGPointValue]];
  457. }
  458. }
  459. else
  460. {
  461. float zoomFactorStep = exp2f(zoomIncr);
  462. [self zoomByFactor:zoomFactorStep near:[[[timer userInfo] objectForKey:@"pivot"] CGPointValue] animated:NO];
  463. if ([callback respondsToSelector:@selector(animationStepped)])
  464. {
  465. [callback animationStepped];
  466. }
  467. }
  468. }
  469. - (void)zoomInToNextNativeZoomAt:(CGPoint) pivot
  470. {
  471. [self zoomInToNextNativeZoomAt:pivot animated:NO];
  472. }
  473. - (float)nextNativeZoomFactor
  474. {
  475. float newZoom = fmin(floorf([self zoom] + 1.0), [self maxZoom]);
  476. return exp2f(newZoom - [self zoom]);
  477. }
  478. - (float)prevNativeZoomFactor
  479. {
  480. float newZoom = fmax(floorf([self zoom] - 1.0), [self minZoom]);
  481. return exp2f(newZoom - [self zoom]);
  482. }
  483. /// \deprecated appears to be unused
  484. - (void)zoomInToNextNativeZoomAt:(CGPoint) pivot animated:(BOOL) animated
  485. {
  486. // Calculate rounded zoom
  487. float newZoom = fmin(floorf([self zoom] + 1.0), [self maxZoom]);
  488. RMLog(@"[self minZoom] %f [self zoom] %f [self maxZoom] %f newzoom %f", [self minZoom], [self zoom], [self maxZoom], newZoom);
  489. float factor = exp2f(newZoom - [self zoom]);
  490. [self zoomByFactor:factor near:pivot animated:animated];
  491. }
  492. /// \deprecated appears to be unused except by zoomOutToNextNativeZoomAt:
  493. - (void)zoomOutToNextNativeZoomAt:(CGPoint) pivot animated:(BOOL) animated {
  494. // Calculate rounded zoom
  495. float newZoom = fmax(ceilf([self zoom] - 1.0), [self minZoom]);
  496. RMLog(@"[self minZoom] %f [self zoom] %f [self maxZoom] %f newzoom %f", [self minZoom], [self zoom], [self maxZoom], newZoom);
  497. float factor = exp2f(newZoom - [self zoom]);
  498. [self zoomByFactor:factor near:pivot animated:animated];
  499. }
  500. /// \deprecated appears to be unused
  501. - (void)zoomOutToNextNativeZoomAt:(CGPoint) pivot {
  502. [self zoomOutToNextNativeZoomAt: pivot animated: FALSE];
  503. }
  504. - (void) drawRect: (CGRect) aRect
  505. {
  506. [renderer drawRect:aRect];
  507. }
  508. -(void)removeAllCachedImages
  509. {
  510. [tileSource removeAllCachedImages];
  511. }
  512. #pragma mark Properties
  513. - (void) setTileSource: (id<RMTileSource>)newTileSource
  514. {
  515. if (tileSource == newTileSource)
  516. return;
  517. RMCachedTileSource *newCachedTileSource = [RMCachedTileSource cachedTileSourceWithSource:newTileSource];
  518. newCachedTileSource = [newCachedTileSource retain];
  519. [tileSource release];
  520. tileSource = newCachedTileSource;
  521. NSAssert(([tileSource minZoom] - minZoom) <= 1.0, @"Graphics & memory are overly taxed if [contents minZoom] is more than 1.5 smaller than [tileSource minZoom]");
  522. [projection release];
  523. projection = [[tileSource projection] retain];
  524. [mercatorToTileProjection release];
  525. mercatorToTileProjection = [[tileSource mercatorToTileProjection] retain];
  526. [imagesOnScreen setTileSource:tileSource];
  527. [tileLoader reset];
  528. [tileLoader reload];
  529. }
  530. - (id<RMTileSource>) tileSource
  531. {
  532. return [[tileSource retain] autorelease];
  533. }
  534. - (void) setRenderer: (RMMapRenderer*) newRenderer
  535. {
  536. if (renderer == newRenderer)
  537. return;
  538. [imagesOnScreen setDelegate:newRenderer];
  539. [[renderer layer] removeFromSuperlayer];
  540. [renderer release];
  541. renderer = [newRenderer retain];
  542. if (renderer == nil)
  543. return;
  544. // CGRect rect = [self screenBounds];
  545. // RMLog(@"%f %f %f %f", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
  546. [[renderer layer] setFrame:[self screenBounds]];
  547. if (background != nil)
  548. [layer insertSublayer:[renderer layer] above:background];
  549. else if (overlay != nil)
  550. [layer insertSublayer:[renderer layer] below:overlay];
  551. else
  552. [layer insertSublayer:[renderer layer] atIndex: 0];
  553. }
  554. - (RMMapRenderer *)renderer
  555. {
  556. return [[renderer retain] autorelease];
  557. }
  558. - (void) setBackground: (RMMapLayer*) aLayer
  559. {
  560. if (background == aLayer) return;
  561. if (background != nil)
  562. {
  563. [background release];
  564. [background removeFromSuperlayer];
  565. }
  566. background = [aLayer retain];
  567. if (background == nil)
  568. return;
  569. background.frame = [self screenBounds];
  570. if ([renderer layer] != nil)
  571. [layer insertSublayer:background below:[renderer layer]];
  572. else if (overlay != nil)
  573. [layer insertSublayer:background below:overlay];
  574. else
  575. [layer insertSublayer:[renderer layer] atIndex: 0];
  576. }
  577. - (RMMapLayer *)background
  578. {
  579. return [[background retain] autorelease];
  580. }
  581. - (void) setOverlay: (RMLayerCollection*) aLayer
  582. {
  583. if (overlay == aLayer) return;
  584. if (overlay != nil)
  585. {
  586. [overlay release];
  587. [overlay removeFromSuperlayer];
  588. }
  589. overlay = [aLayer retain];
  590. if (overlay == nil)
  591. return;
  592. overlay.frame = [self screenBounds];
  593. if ([renderer layer] != nil)
  594. [layer insertSublayer:overlay above:[renderer layer]];
  595. else if (background != nil)
  596. [layer insertSublayer:overlay above:background];
  597. else
  598. [layer insertSublayer:[renderer layer] atIndex: 0];
  599. /* Test to make sure the overlay is working.
  600. CALayer *testLayer = [[CALayer alloc] init];
  601. [testLayer setFrame:CGRectMake(100, 100, 200, 200)];
  602. [testLayer setBackgroundColor:[[UIColor brownColor] CGColor]];
  603. RMLog(@"added test layer");
  604. [overlay addSublayer:testLayer];*/
  605. }
  606. - (RMLayerCollection *)overlay
  607. {
  608. return [[overlay retain] autorelease];
  609. }
  610. - (CLLocationCoordinate2D) mapCenter
  611. {
  612. RMProjectedPoint aPoint = [mercatorToScreenProjection projectedCenter];
  613. return [projection pointToLatLong:aPoint];
  614. }
  615. -(void) setMapCenter: (CLLocationCoordinate2D) center
  616. {
  617. [self moveToLatLong:center];
  618. }
  619. - (RMProjectedPoint)centerProjectedPoint
  620. {
  621. return [mercatorToScreenProjection projectedCenter];
  622. }
  623. - (void)setCenterProjectedPoint:(RMProjectedPoint)projectedPoint
  624. {
  625. [mercatorToScreenProjection setProjectedCenter:projectedPoint];
  626. [overlay correctPositionOfAllSublayers];
  627. [tileLoader reload];
  628. [renderer setNeedsDisplay];
  629. [overlay setNeedsDisplay];
  630. }
  631. -(RMProjectedRect) projectedBounds
  632. {
  633. return [mercatorToScreenProjection projectedBounds];
  634. }
  635. -(void) setProjectedBounds: (RMProjectedRect) boundsRect
  636. {
  637. [mercatorToScreenProjection setProjectedBounds:boundsRect];
  638. }
  639. -(RMTileRect) tileBounds
  640. {
  641. return [mercatorToTileProjection projectRect:[mercatorToScreenProjection projectedBounds]
  642. atScale:[self scaledMetersPerPixel]];
  643. }
  644. -(CGRect) screenBounds
  645. {
  646. if (mercatorToScreenProjection != nil)
  647. return [mercatorToScreenProjection screenBounds];
  648. else
  649. return CGRectZero;
  650. }
  651. -(float) metersPerPixel
  652. {
  653. return [mercatorToScreenProjection metersPerPixel];
  654. }
  655. -(void) setMetersPerPixel: (float) newMPP
  656. {
  657. float zoomFactor = self.metersPerPixel / newMPP;
  658. CGPoint pivot = CGPointZero;
  659. [mercatorToScreenProjection setMetersPerPixel:newMPP];
  660. [imagesOnScreen zoomByFactor:zoomFactor near:pivot];
  661. [tileLoader zoomByFactor:zoomFactor near:pivot];
  662. [overlay zoomByFactor:zoomFactor near:pivot];
  663. [overlay correctPositionOfAllSublayers];
  664. [renderer setNeedsDisplay];
  665. }
  666. -(float) scaledMetersPerPixel
  667. {
  668. return [mercatorToScreenProjection metersPerPixel] / screenScale;
  669. }
  670. - (void)setScaledMetersPerPixel:(float)newMPP {
  671. [self setMetersPerPixel:newMPP * screenScale];
  672. }
  673. -(void)setMaxZoom:(float)newMaxZoom
  674. {
  675. maxZoom = newMaxZoom;
  676. }
  677. -(void)setMinZoom:(float)newMinZoom
  678. {
  679. minZoom = newMinZoom;
  680. NSAssert(!tileSource || (([tileSource minZoom] - minZoom) <= 1.0), @"Graphics & memory are overly taxed if [contents minZoom] is more than 1.5 smaller than [tileSource minZoom]");
  681. }
  682. -(float) zoom
  683. {
  684. return [mercatorToTileProjection calculateZoomFromScale:[self scaledMetersPerPixel]];
  685. }
  686. /// if #zoom is outside of range #minZoom to #maxZoom, zoom level is clamped to that range.
  687. -(void) setZoom: (float) zoom
  688. {
  689. zoom = (zoom > maxZoom) ? maxZoom : zoom;
  690. zoom = (zoom < minZoom) ? minZoom : zoom;
  691. float scale = [mercatorToTileProjection calculateScaleFromZoom:zoom];
  692. [self setScaledMetersPerPixel:scale];
  693. }
  694. -(RMTileImageSet*) imagesOnScreen
  695. {
  696. return [[imagesOnScreen retain] autorelease];
  697. }
  698. -(RMTileLoader*) tileLoader
  699. {
  700. return [[tileLoader retain] autorelease];
  701. }
  702. -(RMProjection*) projection
  703. {
  704. return [[projection retain] autorelease];
  705. }
  706. -(id<RMMercatorToTileProjection>) mercatorToTileProjection
  707. {
  708. return [[mercatorToTileProjection retain] autorelease];
  709. }
  710. -(RMMercatorToScreenProjection*) mercatorToScreenProjection
  711. {
  712. return [[mercatorToScreenProjection retain] autorelease];
  713. }
  714. - (CALayer *)layer
  715. {
  716. return [[layer retain] autorelease];
  717. }
  718. static BOOL _performExpensiveOperations = YES;
  719. + (BOOL) performExpensiveOperations
  720. {
  721. return _performExpensiveOperations;
  722. }
  723. + (void) setPerformExpensiveOperations: (BOOL)p
  724. {
  725. if (p == _performExpensiveOperations)
  726. return;
  727. _performExpensiveOperations = p;
  728. if (p)
  729. [[NSNotificationCenter defaultCenter] postNotificationName:RMResumeExpensiveOperations object:self];
  730. else
  731. [[NSNotificationCenter defaultCenter] postNotificationName:RMSuspendExpensiveOperations object:self];
  732. }
  733. #pragma mark LatLng/Pixel translation functions
  734. - (CGPoint)latLongToPixel:(CLLocationCoordinate2D)latlong
  735. {
  736. return [mercatorToScreenProjection projectXYPoint:[projection latLongToPoint:latlong]];
  737. }
  738. - (CGPoint)latLongToPixel:(CLLocationCoordinate2D)latlong withMetersPerPixel:(float)aScale
  739. {
  740. return [mercatorToScreenProjection projectXYPoint:[projection latLongToPoint:latlong] withMetersPerPixel:aScale];
  741. }
  742. - (RMTilePoint)latLongToTilePoint:(CLLocationCoordinate2D)latlong withMetersPerPixel:(float)aScale
  743. {
  744. return [mercatorToTileProjection project:[projection latLongToPoint:latlong] atZoom:aScale];
  745. }
  746. - (CLLocationCoordinate2D)pixelToLatLong:(CGPoint)aPixel
  747. {
  748. return [projection pointToLatLong:[mercatorToScreenProjection projectScreenPointToXY:aPixel]];
  749. }
  750. - (CLLocationCoordinate2D)pixelToLatLong:(CGPoint)aPixel withMetersPerPixel:(float)aScale
  751. {
  752. return [projection pointToLatLong:[mercatorToScreenProjection projectScreenPointToXY:aPixel withMetersPerPixel:aScale]];
  753. }
  754. - (double)scaleDenominator {
  755. double routemeMetersPerPixel = [self metersPerPixel];
  756. double iphoneMillimetersPerPixel = kiPhoneMilimeteresPerPixel;
  757. double truescaleDenominator = routemeMetersPerPixel / (0.001 * iphoneMillimetersPerPixel) ;
  758. return truescaleDenominator;
  759. }
  760. #pragma mark Zoom With Bounds
  761. - (void)zoomWithLatLngBoundsNorthEast:(CLLocationCoordinate2D)ne SouthWest:(CLLocationCoordinate2D)sw
  762. {
  763. if(ne.latitude == sw.latitude && ne.longitude == sw.longitude)//There are no bounds, probably only one marker.
  764. {
  765. RMProjectedRect zoomRect;
  766. RMProjectedPoint myOrigin = [projection latLongToPoint:sw];
  767. //Default is with scale = 2.0 mercators/pixel
  768. zoomRect.size.width = [self screenBounds].size.width * 2.0;
  769. zoomRect.size.height = [self screenBounds].size.height * 2.0;
  770. myOrigin.easting = myOrigin.easting - (zoomRect.size.width / 2);
  771. myOrigin.northing = myOrigin.northing - (zoomRect.size.height / 2);
  772. zoomRect.origin = myOrigin;
  773. [self zoomWithRMMercatorRectBounds:zoomRect];
  774. }
  775. else
  776. {
  777. //convert ne/sw into RMMercatorRect and call zoomWithBounds
  778. float pixelBuffer = kZoomRectPixelBuffer;
  779. CLLocationCoordinate2D midpoint = {
  780. .latitude = (ne.latitude + sw.latitude) / 2,
  781. .longitude = (ne.longitude + sw.longitude) / 2
  782. };
  783. RMProjectedPoint myOrigin = [projection latLongToPoint:midpoint];
  784. RMProjectedPoint nePoint = [projection latLongToPoint:ne];
  785. RMProjectedPoint swPoint = [projection latLongToPoint:sw];
  786. RMProjectedPoint myPoint = {.easting = nePoint.easting - swPoint.easting, .northing = nePoint.northing - swPoint.northing};
  787. //Create the new zoom layout
  788. RMProjectedRect zoomRect;
  789. //Default is with scale = 2.0 mercators/pixel
  790. zoomRect.size.width = [self screenBounds].size.width * 2.0;
  791. zoomRect.size.height = [self screenBounds].size.height * 2.0;
  792. if((myPoint.easting / ([self screenBounds].size.width)) < (myPoint.northing / ([self screenBounds].size.height)))
  793. {
  794. if((myPoint.northing / ([self screenBounds].size.height - pixelBuffer)) > 1)
  795. {
  796. zoomRect.size.width = [self screenBounds].size.width * (myPoint.northing / ([self screenBounds].size.height - pixelBuffer));
  797. zoomRect.size.height = [self screenBounds].size.height * (myPoint.northing / ([self screenBounds].size.height - pixelBuffer));
  798. }
  799. }
  800. else
  801. {
  802. if((myPoint.easting / ([self screenBounds].size.width - pixelBuffer)) > 1)
  803. {
  804. zoomRect.size.width = [self screenBounds].size.width * (myPoint.easting / ([self screenBounds].size.width - pixelBuffer));
  805. zoomRect.size.height = [self screenBounds].size.height * (myPoint.easting / ([self screenBounds].size.width - pixelBuffer));
  806. }
  807. }
  808. myOrigin.easting = myOrigin.easting - (zoomRect.size.width / 2);
  809. myOrigin.northing = myOrigin.northing - (zoomRect.size.height / 2);
  810. RMLog(@"Origin is calculated at: %f, %f", [projection pointToLatLong:myOrigin].latitude, [projection pointToLatLong:myOrigin].longitude);
  811. /*It gets all messed up if our origin is lower than the lowest place on the map, so we check.
  812. if(myOrigin.northing < -19971868.880409)
  813. {
  814. myOrigin.northing = -19971868.880409;
  815. }*/
  816. zoomRect.origin = myOrigin;
  817. [self zoomWithRMMercatorRectBounds:zoomRect];
  818. }
  819. }
  820. - (void)zoomWithRMMercatorRectBounds:(RMProjectedRect)bounds
  821. {
  822. [self setProjectedBounds:bounds];
  823. [overlay correctPositionOfAllSublayers];
  824. [tileLoader clearLoadedBounds];
  825. [tileLoader updateLoadedImages];
  826. [renderer setNeedsDisplay];
  827. }
  828. #pragma mark Markers and overlays
  829. // Move overlays stuff here - at the moment overlay stuff is above...
  830. - (RMSphericalTrapezium) latitudeLongitudeBoundingBoxForScreen
  831. {
  832. CGRect rect = [mercatorToScreenProjection screenBounds];
  833. return [self latitudeLongitudeBoundingBoxFor:rect];
  834. }
  835. - (RMSphericalTrapezium) latitudeLongitudeBoundingBoxFor:(CGRect) rect
  836. {
  837. RMSphericalTrapezium boundingBox;
  838. CGPoint northwestScreen = rect.origin;
  839. CGPoint southeastScreen;
  840. southeastScreen.x = rect.origin.x + rect.size.width;
  841. southeastScreen.y = rect.origin.y + rect.size.height;
  842. CGPoint northeastScreen, southwestScreen;
  843. northeastScreen.x = southeastScreen.x;
  844. northeastScreen.y = northwestScreen.y;
  845. southwestScreen.x = northwestScreen.x;
  846. southwestScreen.y = southeastScreen.y;
  847. CLLocationCoordinate2D northeastLL, northwestLL, southeastLL, southwestLL;
  848. northeastLL = [self pixelToLatLong:northeastScreen];
  849. northwestLL = [self pixelToLatLong:northwestScreen];
  850. southeastLL = [self pixelToLatLong:southeastScreen];
  851. southwestLL = [self pixelToLatLong:southwestScreen];
  852. boundingBox.northeast.latitude = fmax(northeastLL.latitude, northwestLL.latitude);
  853. boundingBox.southwest.latitude = fmin(southeastLL.latitude, southwestLL.latitude);
  854. // westerly computations:
  855. // -179, -178 -> -179 (min)
  856. // -179, 179 -> 179 (max)
  857. if (fabs(northwestLL.longitude - southwestLL.longitude) <= kMaxLong)
  858. boundingBox.southwest.longitude = fmin(northwestLL.longitude, southwestLL.longitude);
  859. else
  860. boundingBox.southwest.longitude = fmax(northwestLL.longitude, southwestLL.longitude);
  861. if (fabs(northeastLL.longitude - southeastLL.longitude) <= kMaxLong)
  862. boundingBox.northeast.longitude = fmax(northeastLL.longitude, southeastLL.longitude);
  863. else
  864. boundingBox.northeast.longitude = fmin(northeastLL.longitude, southeastLL.longitude);
  865. return boundingBox;
  866. }
  867. - (void) tilesUpdatedRegion:(CGRect)region
  868. {
  869. if(delegateHasRegionUpdate)
  870. {
  871. RMSphericalTrapezium locationBounds = [self latitudeLongitudeBoundingBoxFor:region];
  872. [tilesUpdateDelegate regionUpdate:locationBounds];
  873. }
  874. }
  875. - (void) printDebuggingInformation
  876. {
  877. [imagesOnScreen printDebuggingInformation];
  878. }
  879. @dynamic tilesUpdateDelegate;
  880. - (void) setTilesUpdateDelegate: (id<RMTilesUpdateDelegate>) _tilesUpdateDelegate
  881. {
  882. if (tilesUpdateDelegate == _tilesUpdateDelegate) return;
  883. tilesUpdateDelegate= _tilesUpdateDelegate;
  884. //RMLog(@"Delegate type:%@",[(NSObject *) tilesUpdateDelegate description]);
  885. delegateHasRegionUpdate = [(NSObject*) tilesUpdateDelegate respondsToSelector: @selector(regionUpdate:)];
  886. }
  887. - (id<RMTilesUpdateDelegate>) tilesUpdateDelegate
  888. {
  889. return tilesUpdateDelegate;
  890. }
  891. - (void)setRotation:(float)angle
  892. {
  893. [overlay setRotationOfAllSublayers:(-angle)]; // rotate back markers and paths if theirs allowRotate=NO
  894. }
  895. - (short)tileDepth {
  896. return imagesOnScreen.tileDepth;
  897. }
  898. - (void)setTileDepth:(short)value {
  899. imagesOnScreen.tileDepth = value;
  900. }
  901. - (BOOL)fullyLoaded {
  902. return imagesOnScreen.fullyLoaded;
  903. }
  904. @end