Authored by Hal Mueller

fixed zoomInToNextNativeZoomAt:animated:/zoomOutToNextNativeZoomAt:animated: per…

… mailing list discussions, and Cocoaized some more internal method names
@@ -82,19 +82,19 @@ @@ -82,19 +82,19 @@
82 return nil; 82 return nil;
83 } 83 }
84 84
85 --(RMTileImage *) tileImage: (RMTile)tile 85 +-(RMTileImage *)tileImage:(RMTile)tile
86 { 86 {
87 RMTileImage *image; 87 RMTileImage *image;
88 88
89 tile = [tileProjection normaliseTile:tile]; 89 tile = [tileProjection normaliseTile:tile];
90 - 90 +
91 NSString *file = [self tileFile:tile]; 91 NSString *file = [self tileFile:tile];
92 - 92 +
93 if(file && [[NSFileManager defaultManager] fileExistsAtPath:file]) 93 if(file && [[NSFileManager defaultManager] fileExistsAtPath:file])
94 { 94 {
95 - image = [RMTileImage imageWithTile:tile FromFile:file]; 95 + image = [RMTileImage imageForTile:tile fromFile:file];
96 } else { 96 } else {
97 - image = [RMTileImage imageWithTile:tile FromURL:[self tileURL:tile]]; 97 + image = [RMTileImage imageForTile:tile withURL:[self tileURL:tile]];
98 } 98 }
99 99
100 return image; 100 return image;
@@ -112,7 +112,7 @@ @@ -112,7 +112,7 @@
112 112
113 -(void) didReceiveMemoryWarning 113 -(void) didReceiveMemoryWarning
114 { 114 {
115 - LogMethod(); 115 + LogMethod();
116 } 116 }
117 117
118 -(NSString *)uniqueTilecacheKey 118 -(NSString *)uniqueTilecacheKey
@@ -158,7 +158,7 @@ @@ -158,7 +158,7 @@
158 158
159 } 159 }
160 160
161 - RMTileImage *image = [RMTileImage imageWithTile:tile FromData:data]; 161 + RMTileImage *image = [RMTileImage imageForTile:tile withData:data];
162 // RMLog(@"DB cache hit for tile %d %d %d", tile.x, tile.y, tile.zoom); 162 // RMLog(@"DB cache hit for tile %d %d %d", tile.x, tile.y, tile.zoom);
163 return image; 163 return image;
164 } 164 }
@@ -36,16 +36,16 @@ @@ -36,16 +36,16 @@
36 36
37 \bug lots of arbitrary-appearing \@synchronized blocks. Old mailing list traffic 37 \bug lots of arbitrary-appearing \@synchronized blocks. Old mailing list traffic
38 claims they're needed, but no one seems to know why. If the #set ivar needs to be guarded, 38 claims they're needed, but no one seems to know why. If the #set ivar needs to be guarded,
39 - should be done by \@synchronized(self) and not \@synchronized(set). Maybe the guarding 39 + should be done by \@synchronized(self) and not \@synchronized(sublayers). Maybe the guarding
40 is needed because of Core Animation thread interactions. 40 is needed because of Core Animation thread interactions.
41 */ 41 */
42 -@interface RMLayerSet : RMMapLayer 42 +@interface RMLayerCollection : RMMapLayer
43 { 43 {
44 - /// This is the set of all sublayers, including those offscreen. 44 + /// The actual collection of all sublayers, including those offscreen.
45 /// It is ordered back to front. 45 /// It is ordered back to front.
46 - NSMutableArray *set; 46 + NSMutableArray *sublayers;
47 47
48 - /// We need this reference so we can access the projections... 48 + /// Backpointer to map; we need this reference so we can access the projections...
49 RMMapContents *mapContents; 49 RMMapContents *mapContents;
50 } 50 }
51 51
@@ -25,18 +25,18 @@ @@ -25,18 +25,18 @@
25 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 // POSSIBILITY OF SUCH DAMAGE. 26 // POSSIBILITY OF SUCH DAMAGE.
27 27
28 -#import "RMLayerSet.h" 28 +#import "RMLayerCollection.h"
29 #import "RMMapContents.h" 29 #import "RMMapContents.h"
30 #import "RMMercatorToScreenProjection.h" 30 #import "RMMercatorToScreenProjection.h"
31 31
32 -@implementation RMLayerSet 32 +@implementation RMLayerCollection
33 33
34 - (id)initForContents: (RMMapContents *)_contents 34 - (id)initForContents: (RMMapContents *)_contents
35 { 35 {
36 if (![super init]) 36 if (![super init])
37 return nil; 37 return nil;
38 38
39 - set = [[NSMutableArray alloc] init]; 39 + sublayers = [[NSMutableArray alloc] init];
40 mapContents = _contents; 40 mapContents = _contents;
41 self.masksToBounds = YES; 41 self.masksToBounds = YES;
42 return self; 42 return self;
@@ -44,8 +44,8 @@ @@ -44,8 +44,8 @@
44 44
45 - (void) dealloc 45 - (void) dealloc
46 { 46 {
47 - [set release];  
48 - set = nil; 47 + [sublayers release];
  48 + sublayers = nil;
49 mapContents = nil; 49 mapContents = nil;
50 [super dealloc]; 50 [super dealloc];
51 } 51 }
@@ -69,36 +69,36 @@ @@ -69,36 +69,36 @@
69 { 69 {
70 [self correctScreenPosition:layer]; 70 [self correctScreenPosition:layer];
71 } 71 }
72 -@synchronized(set) {  
73 - [set removeAllObjects];  
74 - [set addObjectsFromArray:array]; 72 +@synchronized(sublayers) {
  73 + [sublayers removeAllObjects];
  74 + [sublayers addObjectsFromArray:array];
75 [super setSublayers:array]; 75 [super setSublayers:array];
76 } 76 }
77 } 77 }
78 78
79 - (void)addSublayer:(CALayer *)layer 79 - (void)addSublayer:(CALayer *)layer
80 { 80 {
81 -@synchronized(set) { 81 +@synchronized(sublayers) {
82 [self correctScreenPosition:layer]; 82 [self correctScreenPosition:layer];
83 - [set addObject:layer]; 83 + [sublayers addObject:layer];
84 [super addSublayer:layer]; 84 [super addSublayer:layer];
85 } 85 }
86 } 86 }
87 87
88 - (void)removeSublayer:(CALayer *)layer 88 - (void)removeSublayer:(CALayer *)layer
89 { 89 {
90 - @synchronized(set) {  
91 - [set removeObject:layer]; 90 + @synchronized(sublayers) {
  91 + [sublayers removeObject:layer];
92 [layer removeFromSuperlayer]; 92 [layer removeFromSuperlayer];
93 } 93 }
94 } 94 }
95 95
96 - (void)removeSublayers:(NSArray *)layers 96 - (void)removeSublayers:(NSArray *)layers
97 { 97 {
98 - @synchronized(set) { 98 + @synchronized(sublayers) {
99 for(CALayer *aLayer in layers) 99 for(CALayer *aLayer in layers)
100 { 100 {
101 - [set removeObject:aLayer]; 101 + [sublayers removeObject:aLayer];
102 [aLayer removeFromSuperlayer]; 102 [aLayer removeFromSuperlayer];
103 } 103 }
104 } 104 }
@@ -106,29 +106,29 @@ @@ -106,29 +106,29 @@
106 106
107 - (void)insertSublayer:(CALayer *)layer above:(CALayer *)siblingLayer 107 - (void)insertSublayer:(CALayer *)layer above:(CALayer *)siblingLayer
108 { 108 {
109 -@synchronized(set) { 109 +@synchronized(sublayers) {
110 [self correctScreenPosition:layer]; 110 [self correctScreenPosition:layer];
111 - NSUInteger index = [set indexOfObject:siblingLayer];  
112 - [set insertObject:layer atIndex:index + 1]; 111 + NSUInteger index = [sublayers indexOfObject:siblingLayer];
  112 + [sublayers insertObject:layer atIndex:index + 1];
113 [super insertSublayer:layer above:siblingLayer]; 113 [super insertSublayer:layer above:siblingLayer];
114 } 114 }
115 } 115 }
116 116
117 - (void)insertSublayer:(CALayer *)layer below:(CALayer *)siblingLayer 117 - (void)insertSublayer:(CALayer *)layer below:(CALayer *)siblingLayer
118 { 118 {
119 -@synchronized(set) { 119 +@synchronized(sublayers) {
120 [self correctScreenPosition:layer]; 120 [self correctScreenPosition:layer];
121 - NSUInteger index = [set indexOfObject:siblingLayer];  
122 - [set insertObject:layer atIndex:index]; 121 + NSUInteger index = [sublayers indexOfObject:siblingLayer];
  122 + [sublayers insertObject:layer atIndex:index];
123 [super insertSublayer:layer below:siblingLayer]; 123 [super insertSublayer:layer below:siblingLayer];
124 } 124 }
125 } 125 }
126 126
127 - (void)insertSublayer:(CALayer *)layer atIndex:(unsigned)index 127 - (void)insertSublayer:(CALayer *)layer atIndex:(unsigned)index
128 { 128 {
129 -@synchronized(set) { 129 +@synchronized(sublayers) {
130 [self correctScreenPosition:layer]; 130 [self correctScreenPosition:layer];
131 - [set insertObject:layer atIndex:index]; 131 + [sublayers insertObject:layer atIndex:index];
132 132
133 /// \bug TODO: Fix this. 133 /// \bug TODO: Fix this.
134 [super addSublayer:layer]; 134 [super addSublayer:layer];
@@ -149,8 +149,8 @@ @@ -149,8 +149,8 @@
149 149
150 - (void)moveBy: (CGSize) delta 150 - (void)moveBy: (CGSize) delta
151 { 151 {
152 - @synchronized(set) {  
153 - for (id layer in set) 152 + @synchronized(sublayers) {
  153 + for (id layer in sublayers)
154 { 154 {
155 if ([layer respondsToSelector:@selector(moveBy:)]) 155 if ([layer respondsToSelector:@selector(moveBy:)])
156 [layer moveBy:delta]; 156 [layer moveBy:delta];
@@ -162,8 +162,8 @@ @@ -162,8 +162,8 @@
162 162
163 - (void)zoomByFactor: (float) zoomFactor near:(CGPoint) center 163 - (void)zoomByFactor: (float) zoomFactor near:(CGPoint) center
164 { 164 {
165 -@synchronized(set) {  
166 - for (id layer in set) 165 +@synchronized(sublayers) {
  166 + for (id layer in sublayers)
167 { 167 {
168 if ([layer respondsToSelector:@selector(zoomByFactor:near:)]) 168 if ([layer respondsToSelector:@selector(zoomByFactor:near:)])
169 [layer zoomByFactor:zoomFactor near:center]; 169 [layer zoomByFactor:zoomFactor near:center];
@@ -173,8 +173,8 @@ @@ -173,8 +173,8 @@
173 173
174 - (void) correctPositionOfAllSublayers 174 - (void) correctPositionOfAllSublayers
175 { 175 {
176 -@synchronized(set) {  
177 - for (id layer in set) 176 +@synchronized(sublayers) {
  177 + for (id layer in sublayers)
178 { 178 {
179 [self correctScreenPosition:layer]; 179 [self correctScreenPosition:layer];
180 } 180 }
@@ -183,7 +183,7 @@ @@ -183,7 +183,7 @@
183 183
184 - (BOOL) hasSubLayer:(CALayer *)layer 184 - (BOOL) hasSubLayer:(CALayer *)layer
185 { 185 {
186 - return [set containsObject:layer]; 186 + return [sublayers containsObject:layer];
187 } 187 }
188 188
189 @end 189 @end
@@ -57,7 +57,7 @@ enum { @@ -57,7 +57,7 @@ enum {
57 @class RMTileLoader; 57 @class RMTileLoader;
58 @class RMMapRenderer; 58 @class RMMapRenderer;
59 @class RMMapLayer; 59 @class RMMapLayer;
60 -@class RMLayerSet; 60 +@class RMLayerCollection;
61 @class RMMarker; 61 @class RMMarker;
62 @protocol RMMercatorToTileProjection; 62 @protocol RMMercatorToTileProjection;
63 @protocol RMTileSource; 63 @protocol RMTileSource;
@@ -89,7 +89,7 @@ enum { @@ -89,7 +89,7 @@ enum {
89 /// subview for the image displayed while tiles are loading. Set its contents by providing your own "loading.png". 89 /// subview for the image displayed while tiles are loading. Set its contents by providing your own "loading.png".
90 RMMapLayer *background; 90 RMMapLayer *background;
91 /// subview for markers and paths 91 /// subview for markers and paths
92 - RMLayerSet *overlay; 92 + RMLayerCollection *overlay;
93 93
94 /// (guess) the projection object to convert from latitude/longitude to meters. 94 /// (guess) the projection object to convert from latitude/longitude to meters.
95 /// Latlong is calculated dynamically from mercatorBounds. 95 /// Latlong is calculated dynamically from mercatorBounds.
@@ -142,7 +142,7 @@ enum { @@ -142,7 +142,7 @@ enum {
142 @property (readonly) CALayer *layer; 142 @property (readonly) CALayer *layer;
143 143
144 @property (retain, readwrite) RMMapLayer *background; 144 @property (retain, readwrite) RMMapLayer *background;
145 -@property (retain, readwrite) RMLayerSet *overlay; 145 +@property (retain, readwrite) RMLayerCollection *overlay;
146 @property (retain, readonly) RMMarkerManager *markerManager; 146 @property (retain, readonly) RMMarkerManager *markerManager;
147 /// \bug probably shouldn't be retaining this delegate 147 /// \bug probably shouldn't be retaining this delegate
148 @property (nonatomic, retain) id<RMTilesUpdateDelegate> tilesUpdateDelegate; 148 @property (nonatomic, retain) id<RMTilesUpdateDelegate> tilesUpdateDelegate;
@@ -42,7 +42,7 @@ @@ -42,7 +42,7 @@
42 #import "RMCoreAnimationRenderer.h" 42 #import "RMCoreAnimationRenderer.h"
43 #import "RMCachedTileSource.h" 43 #import "RMCachedTileSource.h"
44 44
45 -#import "RMLayerSet.h" 45 +#import "RMLayerCollection.h"
46 #import "RMMarkerManager.h" 46 #import "RMMarkerManager.h"
47 47
48 #import "RMMarker.h" 48 #import "RMMarker.h"
@@ -156,7 +156,7 @@ @@ -156,7 +156,7 @@
156 [self setBackground:theBackground]; 156 [self setBackground:theBackground];
157 [theBackground release]; 157 [theBackground release];
158 158
159 - RMLayerSet *theOverlay = [[RMLayerSet alloc] initForContents:self]; 159 + RMLayerCollection *theOverlay = [[RMLayerCollection alloc] initForContents:self];
160 [self setOverlay:theOverlay]; 160 [self setOverlay:theOverlay];
161 [theOverlay release]; 161 [theOverlay release];
162 162
@@ -241,7 +241,7 @@ @@ -241,7 +241,7 @@
241 [self setBackground:theBackground]; 241 [self setBackground:theBackground];
242 [theBackground release]; 242 [theBackground release];
243 243
244 - RMLayerSet *theOverlay = [[RMLayerSet alloc] initForContents:self]; 244 + RMLayerCollection *theOverlay = [[RMLayerCollection alloc] initForContents:self];
245 [self setOverlay:theOverlay]; 245 [self setOverlay:theOverlay];
246 [theOverlay release]; 246 [theOverlay release];
247 247
@@ -540,31 +540,24 @@ @@ -540,31 +540,24 @@
540 - (void)zoomInToNextNativeZoomAt:(CGPoint) pivot animated:(BOOL) animated 540 - (void)zoomInToNextNativeZoomAt:(CGPoint) pivot animated:(BOOL) animated
541 { 541 {
542 // Calculate rounded zoom 542 // Calculate rounded zoom
543 - float newZoom = roundf([self zoom] + 1); 543 + float newZoom = fmin(floorf([self zoom] + 1.0), [self maxZoom]);
  544 + //RMLog(@"[self minZoom] %f [self zoom] %f [self maxZoom] %f newzoom %f", [self minZoom], [self zoom], [self maxZoom], newZoom);
544 545
545 - if (newZoom >= [self maxZoom])  
546 - return;  
547 - else  
548 - {  
549 - float factor = exp2f(newZoom - [self zoom]);  
550 - [self zoomByFactor:factor near:pivot animated:animated];  
551 - } 546 + float factor = exp2f(newZoom - [self zoom]);
  547 + [self zoomByFactor:factor near:pivot animated:animated];
552 } 548 }
553 549
554 - (void)zoomOutToNextNativeZoomAt:(CGPoint) pivot animated:(BOOL) animated { 550 - (void)zoomOutToNextNativeZoomAt:(CGPoint) pivot animated:(BOOL) animated {
555 - // Calculate rounded zoom  
556 - float newZoom = roundf([self zoom] - 1);  
557 -  
558 - if (newZoom <= [self minZoom])  
559 - return;  
560 - else {  
561 - float factor = exp2f(newZoom - [self zoom]);  
562 - [self zoomByFactor:factor near:pivot animated:animated];  
563 - } 551 + // Calculate rounded zoom
  552 + float newZoom = fmax(ceilf([self zoom] - 1.0), [self minZoom]);
  553 + //RMLog(@"[self minZoom] %f [self zoom] %f [self maxZoom] %f newzoom %f", [self minZoom], [self zoom], [self maxZoom], newZoom);
  554 +
  555 + float factor = exp2f(newZoom - [self zoom]);
  556 + [self zoomByFactor:factor near:pivot animated:animated];
564 } 557 }
565 558
566 - (void)zoomOutToNextNativeZoomAt:(CGPoint) pivot { 559 - (void)zoomOutToNextNativeZoomAt:(CGPoint) pivot {
567 - [self zoomOutToNextNativeZoomAt: pivot animated: FALSE]; 560 + [self zoomOutToNextNativeZoomAt: pivot animated: FALSE];
568 } 561 }
569 562
570 563
@@ -677,7 +670,7 @@ @@ -677,7 +670,7 @@
677 return [[background retain] autorelease]; 670 return [[background retain] autorelease];
678 } 671 }
679 672
680 -- (void) setOverlay: (RMLayerSet*) aLayer 673 +- (void) setOverlay: (RMLayerCollection*) aLayer
681 { 674 {
682 if (overlay == aLayer) return; 675 if (overlay == aLayer) return;
683 676
@@ -711,7 +704,7 @@ @@ -711,7 +704,7 @@
711 [overlay addSublayer:testLayer];*/ 704 [overlay addSublayer:testLayer];*/
712 } 705 }
713 706
714 -- (RMLayerSet *)overlay 707 +- (RMLayerCollection *)overlay
715 { 708 {
716 return [[overlay retain] autorelease]; 709 return [[overlay retain] autorelease];
717 } 710 }
@@ -40,7 +40,6 @@ @@ -40,7 +40,6 @@
40 40
41 - (id)initWithContents:(RMMapContents *)mapContents; 41 - (id)initWithContents:(RMMapContents *)mapContents;
42 42
43 -- (void) addMarker: (RMMarker*)marker;  
44 - (void) addMarker: (RMMarker*)marker AtLatLong:(CLLocationCoordinate2D)point; 43 - (void) addMarker: (RMMarker*)marker AtLatLong:(CLLocationCoordinate2D)point;
45 - (void) removeMarkers; 44 - (void) removeMarkers;
46 - (void) hideAllMarkers; 45 - (void) hideAllMarkers;
@@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
28 #import "RMMarkerManager.h" 28 #import "RMMarkerManager.h"
29 #import "RMMercatorToScreenProjection.h" 29 #import "RMMercatorToScreenProjection.h"
30 #import "RMProjection.h" 30 #import "RMProjection.h"
31 -#import "RMLayerSet.h" 31 +#import "RMLayerCollection.h"
32 32
33 @implementation RMMarkerManager 33 @implementation RMMarkerManager
34 34
@@ -54,16 +54,12 @@ @@ -54,16 +54,12 @@
54 #pragma mark 54 #pragma mark
55 #pragma mark Adding / Removing / Displaying Markers 55 #pragma mark Adding / Removing / Displaying Markers
56 56
57 -- (void) addMarker: (RMMarker*)marker  
58 -{  
59 - [[contents overlay] addSublayer:marker];  
60 -}  
61 - 57 +/// place the (newly created) marker onto the map and take ownership of it
62 /// \bug should return the marker 58 /// \bug should return the marker
63 - (void) addMarker: (RMMarker*)marker AtLatLong:(CLLocationCoordinate2D)point 59 - (void) addMarker: (RMMarker*)marker AtLatLong:(CLLocationCoordinate2D)point
64 { 60 {
65 [marker setProjectedLocation:[[contents projection]latLongToPoint:point]]; 61 [marker setProjectedLocation:[[contents projection]latLongToPoint:point]];
66 - [self addMarker: marker]; 62 + [[contents overlay] addSublayer:marker];
67 } 63 }
68 64
69 /// \bug see http://code.google.com/p/route-me/issues/detail?id=75 65 /// \bug see http://code.google.com/p/route-me/issues/detail?id=75
@@ -72,9 +72,9 @@ extern NSString * const RMMapImageLoadingCancelledNotification; @@ -72,9 +72,9 @@ extern NSString * const RMMapImageLoadingCancelledNotification;
72 //- (id) increaseLoadingPriority; 72 //- (id) increaseLoadingPriority;
73 //- (id) decreaseLoadingPriority; 73 //- (id) decreaseLoadingPriority;
74 74
75 -+ (RMTileImage*)imageWithTile: (RMTile) tile FromURL: (NSString*)url;  
76 -+ (RMTileImage*)imageWithTile: (RMTile) tile FromFile: (NSString*)filename;  
77 -+ (RMTileImage*)imageWithTile: (RMTile) tile FromData: (NSData*)data; 75 ++ (RMTileImage*)imageForTile: (RMTile) tile withURL: (NSString*)url;
  76 ++ (RMTileImage*)imageForTile: (RMTile) tile fromFile: (NSString*)filename;
  77 ++ (RMTileImage*)imageForTile: (RMTile) tile withData: (NSData*)data;
78 78
79 - (void)drawInRect:(CGRect)rect; 79 - (void)drawInRect:(CGRect)rect;
80 - (void)draw; 80 - (void)draw;
@@ -86,7 +86,7 @@ extern NSString * const RMMapImageLoadingCancelledNotification; @@ -86,7 +86,7 @@ extern NSString * const RMMapImageLoadingCancelledNotification;
86 86
87 - (void)cancelLoading; 87 - (void)cancelLoading;
88 88
89 -- (void)setImageToData: (NSData*) data; 89 +- (void)updateImageUsingData: (NSData*) data;
90 90
91 - (void)touch; 91 - (void)touch;
92 92
@@ -128,20 +128,20 @@ NSString * const RMMapImageLoadingCancelledNotification = @"MapImageLoadingCance @@ -128,20 +128,20 @@ NSString * const RMMapImageLoadingCancelledNotification = @"MapImageLoadingCance
128 [self drawInRect:screenLocation]; 128 [self drawInRect:screenLocation];
129 } 129 }
130 130
131 -+ (RMTileImage*)imageWithTile: (RMTile) _tile FromURL: (NSString*)url 131 ++ (RMTileImage*)imageForTile:(RMTile) _tile withURL: (NSString*)url
132 { 132 {
133 return [[[RMWebTileImage alloc] initWithTile:_tile FromURL:url] autorelease]; 133 return [[[RMWebTileImage alloc] initWithTile:_tile FromURL:url] autorelease];
134 } 134 }
135 135
136 -+ (RMTileImage*)imageWithTile: (RMTile) _tile FromFile: (NSString*)filename 136 ++ (RMTileImage*)imageForTile:(RMTile) _tile fromFile: (NSString*)filename
137 { 137 {
138 return [[[RMFileTileImage alloc] initWithTile: _tile FromFile:filename] autorelease]; 138 return [[[RMFileTileImage alloc] initWithTile: _tile FromFile:filename] autorelease];
139 } 139 }
140 140
141 -+ (RMTileImage*)imageWithTile: (RMTile) tile FromData: (NSData*)data 141 ++ (RMTileImage*)imageForTile:(RMTile) tile withData: (NSData*)data
142 { 142 {
143 RMTileImage *image = [[RMTileImage alloc] initWithTile:tile]; 143 RMTileImage *image = [[RMTileImage alloc] initWithTile:tile];
144 - [image setImageToData:data]; 144 + [image updateImageUsingData:data];
145 return [image autorelease]; 145 return [image autorelease];
146 } 146 }
147 147
@@ -155,7 +155,7 @@ NSString * const RMMapImageLoadingCancelledNotification = @"MapImageLoadingCance @@ -155,7 +155,7 @@ NSString * const RMMapImageLoadingCancelledNotification = @"MapImageLoadingCance
155 { 155 {
156 if (dataPending != nil) 156 if (dataPending != nil)
157 { 157 {
158 - [self setImageToData:dataPending]; 158 + [self updateImageUsingData:dataPending];
159 [dataPending release]; 159 [dataPending release];
160 dataPending = nil; 160 dataPending = nil;
161 161
@@ -165,7 +165,7 @@ NSString * const RMMapImageLoadingCancelledNotification = @"MapImageLoadingCance @@ -165,7 +165,7 @@ NSString * const RMMapImageLoadingCancelledNotification = @"MapImageLoadingCance
165 [[NSNotificationCenter defaultCenter] removeObserver:self name:RMResumeExpensiveOperations object:nil]; 165 [[NSNotificationCenter defaultCenter] removeObserver:self name:RMResumeExpensiveOperations object:nil];
166 } 166 }
167 167
168 -- (void)setImageToData: (NSData*) data 168 +- (void)updateImageUsingData: (NSData*) data
169 { 169 {
170 if ([RMMapContents performExpensiveOperations] == NO) 170 if ([RMMapContents performExpensiveOperations] == NO)
171 { 171 {
@@ -51,7 +51,7 @@ static RMTileImage *_loadingTile = nil; @@ -51,7 +51,7 @@ static RMTileImage *_loadingTile = nil;
51 RMTile t = RMTileDummy(); 51 RMTile t = RMTileDummy();
52 /// \bug magic string literals 52 /// \bug magic string literals
53 NSString* file = [[NSBundle mainBundle] pathForResource:@"loading" ofType:@"png"]; 53 NSString* file = [[NSBundle mainBundle] pathForResource:@"loading" ofType:@"png"];
54 - _loadingTile = [[RMTileImage imageWithTile:t FromFile:file] retain]; 54 + _loadingTile = [[RMTileImage imageForTile:t fromFile:file] retain];
55 return _loadingTile; 55 return _loadingTile;
56 // return nil; 56 // return nil;
57 } 57 }
@@ -54,7 +54,7 @@ @@ -54,7 +54,7 @@
54 if (cachedData != nil) 54 if (cachedData != nil)
55 { 55 {
56 // RMLog(@"Using cached image"); 56 // RMLog(@"Using cached image");
57 - [self setImageToData:[cachedData data]]; 57 + [self updateImageUsingData:[cachedData data]];
58 } 58 }
59 else 59 else
60 { 60 {
@@ -194,7 +194,7 @@ didReceiveResponse:(NSURLResponse *)response @@ -194,7 +194,7 @@ didReceiveResponse:(NSURLResponse *)response
194 194
195 - (void)connectionDidFinishLoading:(NSURLConnection *)_connection 195 - (void)connectionDidFinishLoading:(NSURLConnection *)_connection
196 { 196 {
197 - [self setImageToData:data]; 197 + [self updateImageUsingData:data];
198 198
199 [data release]; 199 [data release];
200 data = nil; 200 data = nil;
@@ -62,7 +62,7 @@ @@ -62,7 +62,7 @@
62 2BEC60370F8AC718008FB858 /* RMFoundation.c in Sources */ = {isa = PBXBuildFile; fileRef = 23A0AAE80EB90A99003A4521 /* RMFoundation.c */; }; 62 2BEC60370F8AC718008FB858 /* RMFoundation.c in Sources */ = {isa = PBXBuildFile; fileRef = 23A0AAE80EB90A99003A4521 /* RMFoundation.c */; };
63 2BEC60380F8AC71A008FB858 /* RMFractalTileProjection.m in Sources */ = {isa = PBXBuildFile; fileRef = B83E64EA0E80E73F001663B6 /* RMFractalTileProjection.m */; }; 63 2BEC60380F8AC71A008FB858 /* RMFractalTileProjection.m in Sources */ = {isa = PBXBuildFile; fileRef = B83E64EA0E80E73F001663B6 /* RMFractalTileProjection.m */; };
64 2BEC60390F8AC71C008FB858 /* RMGeoHash.m in Sources */ = {isa = PBXBuildFile; fileRef = F5C12D290F8A86CA00A894D2 /* RMGeoHash.m */; }; 64 2BEC60390F8AC71C008FB858 /* RMGeoHash.m in Sources */ = {isa = PBXBuildFile; fileRef = F5C12D290F8A86CA00A894D2 /* RMGeoHash.m */; };
65 - 2BEC603A0F8AC71D008FB858 /* RMLayerSet.m in Sources */ = {isa = PBXBuildFile; fileRef = B8FA92180E9315EC003A9FE6 /* RMLayerSet.m */; }; 65 + 2BEC603A0F8AC71D008FB858 /* RMLayerCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = B8FA92180E9315EC003A9FE6 /* RMLayerCollection.m */; };
66 2BEC603B0F8AC71E008FB858 /* RMMapContents.m in Sources */ = {isa = PBXBuildFile; fileRef = B8C974B70E8A280A007D16AD /* RMMapContents.m */; }; 66 2BEC603B0F8AC71E008FB858 /* RMMapContents.m in Sources */ = {isa = PBXBuildFile; fileRef = B8C974B70E8A280A007D16AD /* RMMapContents.m */; };
67 2BEC603C0F8AC71F008FB858 /* RMMapLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = B8F3FC600EA2B382004D8F85 /* RMMapLayer.m */; }; 67 2BEC603C0F8AC71F008FB858 /* RMMapLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = B8F3FC600EA2B382004D8F85 /* RMMapLayer.m */; };
68 2BEC603D0F8AC722008FB858 /* RMMapRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = B83E64CB0E80E73F001663B6 /* RMMapRenderer.m */; }; 68 2BEC603D0F8AC722008FB858 /* RMMapRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = B83E64CB0E80E73F001663B6 /* RMMapRenderer.m */; };
@@ -173,8 +173,8 @@ @@ -173,8 +173,8 @@
173 B8F3FC610EA2B382004D8F85 /* RMMapLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = B8F3FC600EA2B382004D8F85 /* RMMapLayer.m */; }; 173 B8F3FC610EA2B382004D8F85 /* RMMapLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = B8F3FC600EA2B382004D8F85 /* RMMapLayer.m */; };
174 B8F3FC640EA2E792004D8F85 /* RMMarker.h in Headers */ = {isa = PBXBuildFile; fileRef = B8F3FC620EA2E792004D8F85 /* RMMarker.h */; }; 174 B8F3FC640EA2E792004D8F85 /* RMMarker.h in Headers */ = {isa = PBXBuildFile; fileRef = B8F3FC620EA2E792004D8F85 /* RMMarker.h */; };
175 B8F3FC650EA2E792004D8F85 /* RMMarker.m in Sources */ = {isa = PBXBuildFile; fileRef = B8F3FC630EA2E792004D8F85 /* RMMarker.m */; }; 175 B8F3FC650EA2E792004D8F85 /* RMMarker.m in Sources */ = {isa = PBXBuildFile; fileRef = B8F3FC630EA2E792004D8F85 /* RMMarker.m */; };
176 - B8FA92190E9315EC003A9FE6 /* RMLayerSet.h in Headers */ = {isa = PBXBuildFile; fileRef = B8FA92170E9315EC003A9FE6 /* RMLayerSet.h */; };  
177 - B8FA921A0E9315EC003A9FE6 /* RMLayerSet.m in Sources */ = {isa = PBXBuildFile; fileRef = B8FA92180E9315EC003A9FE6 /* RMLayerSet.m */; }; 176 + B8FA92190E9315EC003A9FE6 /* RMLayerCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = B8FA92170E9315EC003A9FE6 /* RMLayerCollection.h */; };
  177 + B8FA921A0E9315EC003A9FE6 /* RMLayerCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = B8FA92180E9315EC003A9FE6 /* RMLayerCollection.m */; };
178 F5C12D2A0F8A86CA00A894D2 /* RMGeoHash.h in Headers */ = {isa = PBXBuildFile; fileRef = F5C12D280F8A86CA00A894D2 /* RMGeoHash.h */; }; 178 F5C12D2A0F8A86CA00A894D2 /* RMGeoHash.h in Headers */ = {isa = PBXBuildFile; fileRef = F5C12D280F8A86CA00A894D2 /* RMGeoHash.h */; };
179 F5C12D2B0F8A86CA00A894D2 /* RMGeoHash.m in Sources */ = {isa = PBXBuildFile; fileRef = F5C12D290F8A86CA00A894D2 /* RMGeoHash.m */; }; 179 F5C12D2B0F8A86CA00A894D2 /* RMGeoHash.m in Sources */ = {isa = PBXBuildFile; fileRef = F5C12D290F8A86CA00A894D2 /* RMGeoHash.m */; };
180 /* End PBXBuildFile section */ 180 /* End PBXBuildFile section */
@@ -326,8 +326,8 @@ @@ -326,8 +326,8 @@
326 B8F3FC600EA2B382004D8F85 /* RMMapLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMMapLayer.m; sourceTree = "<group>"; }; 326 B8F3FC600EA2B382004D8F85 /* RMMapLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMMapLayer.m; sourceTree = "<group>"; };
327 B8F3FC620EA2E792004D8F85 /* RMMarker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMMarker.h; sourceTree = "<group>"; }; 327 B8F3FC620EA2E792004D8F85 /* RMMarker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMMarker.h; sourceTree = "<group>"; };
328 B8F3FC630EA2E792004D8F85 /* RMMarker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMMarker.m; sourceTree = "<group>"; }; 328 B8F3FC630EA2E792004D8F85 /* RMMarker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMMarker.m; sourceTree = "<group>"; };
329 - B8FA92170E9315EC003A9FE6 /* RMLayerSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMLayerSet.h; sourceTree = "<group>"; };  
330 - B8FA92180E9315EC003A9FE6 /* RMLayerSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMLayerSet.m; sourceTree = "<group>"; }; 329 + B8FA92170E9315EC003A9FE6 /* RMLayerCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMLayerCollection.h; sourceTree = "<group>"; };
  330 + B8FA92180E9315EC003A9FE6 /* RMLayerCollection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMLayerCollection.m; sourceTree = "<group>"; };
331 C7A967500E8412930031BA75 /* RMAbstractMercatorWebSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMAbstractMercatorWebSource.h; sourceTree = "<group>"; }; 331 C7A967500E8412930031BA75 /* RMAbstractMercatorWebSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMAbstractMercatorWebSource.h; sourceTree = "<group>"; };
332 C7A967510E8412930031BA75 /* RMAbstractMercatorWebSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMAbstractMercatorWebSource.m; sourceTree = "<group>"; }; 332 C7A967510E8412930031BA75 /* RMAbstractMercatorWebSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMAbstractMercatorWebSource.m; sourceTree = "<group>"; };
333 C7A9675C0E84134B0031BA75 /* RMVirtualEarthSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMVirtualEarthSource.h; sourceTree = "<group>"; }; 333 C7A9675C0E84134B0031BA75 /* RMVirtualEarthSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMVirtualEarthSource.h; sourceTree = "<group>"; };
@@ -631,8 +631,8 @@ @@ -631,8 +631,8 @@
631 090C948C0EC23FCD003AEE25 /* RMMarkerManager.m */, 631 090C948C0EC23FCD003AEE25 /* RMMarkerManager.m */,
632 B86F26AC0E87442C007A3773 /* RMMapLayer.h */, 632 B86F26AC0E87442C007A3773 /* RMMapLayer.h */,
633 B8F3FC600EA2B382004D8F85 /* RMMapLayer.m */, 633 B8F3FC600EA2B382004D8F85 /* RMMapLayer.m */,
634 - B8FA92170E9315EC003A9FE6 /* RMLayerSet.h */,  
635 - B8FA92180E9315EC003A9FE6 /* RMLayerSet.m */, 634 + B8FA92170E9315EC003A9FE6 /* RMLayerCollection.h */,
  635 + B8FA92180E9315EC003A9FE6 /* RMLayerCollection.m */,
636 B8F3FC620EA2E792004D8F85 /* RMMarker.h */, 636 B8F3FC620EA2E792004D8F85 /* RMMarker.h */,
637 B8F3FC630EA2E792004D8F85 /* RMMarker.m */, 637 B8F3FC630EA2E792004D8F85 /* RMMarker.m */,
638 B8CEB1C30ED5A3480014C431 /* RMPath.h */, 638 B8CEB1C30ED5A3480014C431 /* RMPath.h */,
@@ -722,7 +722,7 @@ @@ -722,7 +722,7 @@
722 B8C974B00E8A1F08007D16AD /* RMMercatorToTileProjection.h in Headers */, 722 B8C974B00E8A1F08007D16AD /* RMMercatorToTileProjection.h in Headers */,
723 B8C974B80E8A280A007D16AD /* RMMapContents.h in Headers */, 723 B8C974B80E8A280A007D16AD /* RMMapContents.h in Headers */,
724 B8C974CA0E8A9C30007D16AD /* RMCachedTileSource.h in Headers */, 724 B8C974CA0E8A9C30007D16AD /* RMCachedTileSource.h in Headers */,
725 - B8FA92190E9315EC003A9FE6 /* RMLayerSet.h in Headers */, 725 + B8FA92190E9315EC003A9FE6 /* RMLayerCollection.h in Headers */,
726 B8F3FC640EA2E792004D8F85 /* RMMarker.h in Headers */, 726 B8F3FC640EA2E792004D8F85 /* RMMarker.h in Headers */,
727 B8474B9A0EB40094006A0BC1 /* FMDatabase.h in Headers */, 727 B8474B9A0EB40094006A0BC1 /* FMDatabase.h in Headers */,
728 B8474B9C0EB40094006A0BC1 /* FMDatabaseAdditions.h in Headers */, 728 B8474B9C0EB40094006A0BC1 /* FMDatabaseAdditions.h in Headers */,
@@ -892,7 +892,7 @@ @@ -892,7 +892,7 @@
892 2BEC60370F8AC718008FB858 /* RMFoundation.c in Sources */, 892 2BEC60370F8AC718008FB858 /* RMFoundation.c in Sources */,
893 2BEC60380F8AC71A008FB858 /* RMFractalTileProjection.m in Sources */, 893 2BEC60380F8AC71A008FB858 /* RMFractalTileProjection.m in Sources */,
894 2BEC60390F8AC71C008FB858 /* RMGeoHash.m in Sources */, 894 2BEC60390F8AC71C008FB858 /* RMGeoHash.m in Sources */,
895 - 2BEC603A0F8AC71D008FB858 /* RMLayerSet.m in Sources */, 895 + 2BEC603A0F8AC71D008FB858 /* RMLayerCollection.m in Sources */,
896 2BEC603B0F8AC71E008FB858 /* RMMapContents.m in Sources */, 896 2BEC603B0F8AC71E008FB858 /* RMMapContents.m in Sources */,
897 2BEC603C0F8AC71F008FB858 /* RMMapLayer.m in Sources */, 897 2BEC603C0F8AC71F008FB858 /* RMMapLayer.m in Sources */,
898 2BEC603D0F8AC722008FB858 /* RMMapRenderer.m in Sources */, 898 2BEC603D0F8AC722008FB858 /* RMMapRenderer.m in Sources */,
@@ -954,7 +954,7 @@ @@ -954,7 +954,7 @@
954 B8C9746C0E8A1A50007D16AD /* RMMapView.m in Sources */, 954 B8C9746C0E8A1A50007D16AD /* RMMapView.m in Sources */,
955 B8C974B90E8A280A007D16AD /* RMMapContents.m in Sources */, 955 B8C974B90E8A280A007D16AD /* RMMapContents.m in Sources */,
956 B8C974CB0E8A9C30007D16AD /* RMCachedTileSource.m in Sources */, 956 B8C974CB0E8A9C30007D16AD /* RMCachedTileSource.m in Sources */,
957 - B8FA921A0E9315EC003A9FE6 /* RMLayerSet.m in Sources */, 957 + B8FA921A0E9315EC003A9FE6 /* RMLayerCollection.m in Sources */,
958 B8F3FC610EA2B382004D8F85 /* RMMapLayer.m in Sources */, 958 B8F3FC610EA2B382004D8F85 /* RMMapLayer.m in Sources */,
959 B8F3FC650EA2E792004D8F85 /* RMMarker.m in Sources */, 959 B8F3FC650EA2E792004D8F85 /* RMMarker.m in Sources */,
960 B8474B9B0EB40094006A0BC1 /* FMDatabase.m in Sources */, 960 B8474B9B0EB40094006A0BC1 /* FMDatabase.m in Sources */,
@@ -314,12 +314,17 @@ @@ -314,12 +314,17 @@
314 STAssertLessThanOrEqual(contentsMaxZoom, tilesourceMaxZoom, @"map's maxZoom exceeds tilesource's maxZoom"); 314 STAssertLessThanOrEqual(contentsMaxZoom, tilesourceMaxZoom, @"map's maxZoom exceeds tilesource's maxZoom");
315 STAssertLessThanOrEqual(contentsZoom, tilesourceMaxZoom, @"map's zoom exceeds tilesource's maxZoom"); 315 STAssertLessThanOrEqual(contentsZoom, tilesourceMaxZoom, @"map's zoom exceeds tilesource's maxZoom");
316 316
317 - double targetZoom = tilesourceMaxZoom + 0.5; 317 + double targetZoom = tilesourceMaxZoom + 0.4;
318 [[mapView contents] setZoom:targetZoom]; // try to exceed tilesource limit 318 [[mapView contents] setZoom:targetZoom]; // try to exceed tilesource limit
319 contentsZoom = [[mapView contents] zoom]; 319 contentsZoom = [[mapView contents] zoom];
320 STAssertEqualsWithAccuracy(contentsZoom, tilesourceMaxZoom, kAccuracyThreshold, @"map's zoom wrong after trying to exceed tilesource's maxZoom"); 320 STAssertEqualsWithAccuracy(contentsZoom, tilesourceMaxZoom, kAccuracyThreshold, @"map's zoom wrong after trying to exceed tilesource's maxZoom");
321 -  
322 - targetZoom = tilesourceMaxZoom - 1.5; 321 +
  322 + targetZoom = tilesourceMaxZoom + 0.5;
  323 + [[mapView contents] setZoom:targetZoom]; // try to exceed tilesource limit
  324 + contentsZoom = [[mapView contents] zoom];
  325 + STAssertEqualsWithAccuracy(contentsZoom, tilesourceMaxZoom, kAccuracyThreshold, @"map's zoom wrong after trying to exceed tilesource's maxZoom");
  326 +
  327 + targetZoom = tilesourceMaxZoom - 1.6;
323 [[mapView contents] setZoom:targetZoom]; 328 [[mapView contents] setZoom:targetZoom];
324 CGPoint pivotPoint = CGPointMake(5., 5.); 329 CGPoint pivotPoint = CGPointMake(5., 5.);
325 [[mapView contents] zoomInToNextNativeZoomAt:pivotPoint]; 330 [[mapView contents] zoomInToNextNativeZoomAt:pivotPoint];
@@ -339,6 +344,26 @@ @@ -339,6 +344,26 @@
339 contentsZoom, tilesourceMaxZoom); 344 contentsZoom, tilesourceMaxZoom);
340 345
341 346
  347 + targetZoom = tilesourceMaxZoom - 1.5;
  348 + [[mapView contents] setZoom:targetZoom];
  349 + pivotPoint = CGPointMake(5., 5.);
  350 + [[mapView contents] zoomInToNextNativeZoomAt:pivotPoint];
  351 + contentsZoom = [[mapView contents] zoom];
  352 + STAssertEqualsWithAccuracy(contentsZoom, tilesourceMaxZoom - 1.0, kAccuracyThreshold,
  353 + @"map's zoom %f wrong after zoomInToNextNativeZoomAt: for maxZoom-1 %f",
  354 + contentsZoom, tilesourceMaxZoom);
  355 + [[mapView contents] zoomInToNextNativeZoomAt:pivotPoint];
  356 + contentsZoom = [[mapView contents] zoom];
  357 + STAssertEqualsWithAccuracy(contentsZoom, tilesourceMaxZoom, kAccuracyThreshold,
  358 + @"map's zoom [%f] wrong after zoomInToNextNativeZoomAt: for maxZoom %f (first)",
  359 + contentsZoom, tilesourceMaxZoom);
  360 + [[mapView contents] zoomInToNextNativeZoomAt:pivotPoint];
  361 + contentsZoom = [[mapView contents] zoom];
  362 + STAssertEqualsWithAccuracy(contentsZoom, tilesourceMaxZoom, kAccuracyThreshold,
  363 + @"map's zoom %f wrong after zoomInToNextNativeZoomAt: for maxZoom %f (second)",
  364 + contentsZoom, tilesourceMaxZoom);
  365 +
  366 +
342 double contentsMinZoom = 3.0; 367 double contentsMinZoom = 3.0;
343 [[mapView contents] setMinZoom:contentsMinZoom]; 368 [[mapView contents] setMinZoom:contentsMinZoom];
344 targetZoom = contentsMinZoom + 0.6; 369 targetZoom = contentsMinZoom + 0.6;
@@ -356,7 +381,29 @@ @@ -356,7 +381,29 @@
356 @"map's zoom %f wrong after second zoomOutToNextNativeZoomAt: for minZoom %f", 381 @"map's zoom %f wrong after second zoomOutToNextNativeZoomAt: for minZoom %f",
357 contentsZoom, contentsMinZoom); 382 contentsZoom, contentsMinZoom);
358 383
359 - 384 + contentsMinZoom = 3.0;
  385 + [[mapView contents] setMinZoom:contentsMinZoom];
  386 + targetZoom = contentsMinZoom + 1.6;
  387 + [[mapView contents] setZoom:targetZoom];
  388 + contentsZoom = [[mapView contents] zoom];
  389 + NSLog(@"zoom: %f minZoom: %f", contentsZoom, [[mapView contents] minZoom]);
  390 + [[mapView contents] zoomOutToNextNativeZoomAt:pivotPoint];
  391 + contentsZoom = [[mapView contents] zoom];
  392 + STAssertEqualsWithAccuracy(contentsZoom, contentsMinZoom+1.0, kAccuracyThreshold,
  393 + @"map's zoom %f wrong after first zoomOutToNextNativeZoomAt: for minZoom %f",
  394 + contentsZoom, contentsMinZoom);
  395 + [[mapView contents] zoomOutToNextNativeZoomAt:pivotPoint];
  396 + contentsZoom = [[mapView contents] zoom];
  397 + STAssertEqualsWithAccuracy(contentsZoom, contentsMinZoom, kAccuracyThreshold,
  398 + @"map's zoom %f wrong after second zoomOutToNextNativeZoomAt: for minZoom %f",
  399 + contentsZoom, contentsMinZoom);
  400 + [[mapView contents] zoomOutToNextNativeZoomAt:pivotPoint];
  401 + contentsZoom = [[mapView contents] zoom];
  402 + STAssertEqualsWithAccuracy(contentsZoom, contentsMinZoom, kAccuracyThreshold,
  403 + @"map's zoom %f wrong after second zoomOutToNextNativeZoomAt: for minZoom %f",
  404 + contentsZoom, contentsMinZoom);
  405 +
  406 +
360 } 407 }
361 408
362 @end 409 @end