Authored by Thomas Rasch

o Merge local changes

@@ -171,7 +171,7 @@ @@ -171,7 +171,7 @@
171 return NO; 171 return NO;
172 } 172 }
173 173
174 -- (void) compainAboutInUse { 174 +- (void) complainAboutInUse {
175 NSLog(@"The FMDatabase %@ is currently in use.", self); 175 NSLog(@"The FMDatabase %@ is currently in use.", self);
176 176
177 if (crashOnErrors) { 177 if (crashOnErrors) {
@@ -196,7 +196,7 @@ @@ -196,7 +196,7 @@
196 - (sqlite_int64) lastInsertRowId { 196 - (sqlite_int64) lastInsertRowId {
197 197
198 if (inUse) { 198 if (inUse) {
199 - [self compainAboutInUse]; 199 + [self complainAboutInUse];
200 return NO; 200 return NO;
201 } 201 }
202 [self setInUse:YES]; 202 [self setInUse:YES];
@@ -216,7 +216,7 @@ @@ -216,7 +216,7 @@
216 216
217 // FIXME - someday check the return codes on these binds. 217 // FIXME - someday check the return codes on these binds.
218 else if ([obj isKindOfClass:[NSData class]]) { 218 else if ([obj isKindOfClass:[NSData class]]) {
219 - sqlite3_bind_blob(pStmt, idx, [obj bytes], (int)[obj length], SQLITE_STATIC); 219 + sqlite3_bind_blob(pStmt, idx, [obj bytes], (int)[(NSData *)obj length], SQLITE_STATIC);
220 } 220 }
221 else if ([obj isKindOfClass:[NSDate class]]) { 221 else if ([obj isKindOfClass:[NSDate class]]) {
222 sqlite3_bind_double(pStmt, idx, [obj timeIntervalSince1970]); 222 sqlite3_bind_double(pStmt, idx, [obj timeIntervalSince1970]);
@@ -253,7 +253,7 @@ @@ -253,7 +253,7 @@
253 - (id) executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orVAList:(va_list)args { 253 - (id) executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orVAList:(va_list)args {
254 254
255 if (inUse) { 255 if (inUse) {
256 - [self compainAboutInUse]; 256 + [self complainAboutInUse];
257 return nil; 257 return nil;
258 } 258 }
259 259
@@ -387,7 +387,7 @@ @@ -387,7 +387,7 @@
387 - (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray*)arrayArgs orVAList:(va_list)args { 387 - (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray*)arrayArgs orVAList:(va_list)args {
388 388
389 if (inUse) { 389 if (inUse) {
390 - [self compainAboutInUse]; 390 + [self complainAboutInUse];
391 return NO; 391 return NO;
392 } 392 }
393 393
@@ -38,6 +38,8 @@ @@ -38,6 +38,8 @@
38 RMTileCache *cache; 38 RMTileCache *cache;
39 } 39 }
40 40
  41 +@property (nonatomic, readonly) RMTileCache *cache;
  42 +
41 - (id) initWithSource: (id<RMTileSource>) source; 43 - (id) initWithSource: (id<RMTileSource>) source;
42 - (void) didReceiveMemoryWarning; 44 - (void) didReceiveMemoryWarning;
43 45
@@ -30,6 +30,8 @@ @@ -30,6 +30,8 @@
30 30
31 @implementation RMCachedTileSource 31 @implementation RMCachedTileSource
32 32
  33 +@synthesize cache;
  34 +
33 - (id) initWithSource: (id<RMTileSource>) _source 35 - (id) initWithSource: (id<RMTileSource>) _source
34 { 36 {
35 if ([_source isKindOfClass:[RMCachedTileSource class]]) 37 if ([_source isKindOfClass:[RMCachedTileSource class]])
@@ -107,12 +107,10 @@ @@ -107,12 +107,10 @@
107 self = [super init]; 107 self = [super init];
108 if (self != nil) { 108 if (self != nil) {
109 // open the db 109 // open the db
110 - NSString* fullPath = [[NSBundle mainBundle] pathForResource:path ofType:nil];  
111 - NSLog(@"Trying to Open db map source %@", fullPath);  
112 - db = [[FMDatabase alloc] initWithPath:fullPath];  
113 - if ([db open]) { 110 + db = [[FMDatabase alloc] initWithPath:path];
  111 + if ([db openWithFlags:SQLITE_OPEN_READONLY]) {
114 RMLog(@"Opening db map source %@", path); 112 RMLog(@"Opening db map source %@", path);
115 - 113 +
116 // get the tile side length 114 // get the tile side length
117 tileSideLength = [self getPreferenceAsInt:kTileSideLengthKey]; 115 tileSideLength = [self getPreferenceAsInt:kTileSideLengthKey];
118 116
@@ -124,12 +122,12 @@ @@ -124,12 +122,12 @@
124 topLeft.latitude = [self getPreferenceAsFloat:kCoverageTopLeftLatitudeKey]; 122 topLeft.latitude = [self getPreferenceAsFloat:kCoverageTopLeftLatitudeKey];
125 topLeft.longitude = [self getPreferenceAsFloat:kCoverageTopLeftLongitudeKey]; 123 topLeft.longitude = [self getPreferenceAsFloat:kCoverageTopLeftLongitudeKey];
126 bottomRight.latitude = [self getPreferenceAsFloat:kCoverageBottomRightLatitudeKey]; 124 bottomRight.latitude = [self getPreferenceAsFloat:kCoverageBottomRightLatitudeKey];
127 - bottomRight.longitude = [self getPreferenceAsFloat:kCoverageBottomRightLatitudeKey]; 125 + bottomRight.longitude = [self getPreferenceAsFloat:kCoverageBottomRightLongitudeKey];
128 center.latitude = [self getPreferenceAsFloat:kCoverageCenterLatitudeKey]; 126 center.latitude = [self getPreferenceAsFloat:kCoverageCenterLatitudeKey];
129 center.longitude = [self getPreferenceAsFloat:kCoverageCenterLongitudeKey]; 127 center.longitude = [self getPreferenceAsFloat:kCoverageCenterLongitudeKey];
130 128
131 RMLog(@"Tile size: %d pixel", tileSideLength); 129 RMLog(@"Tile size: %d pixel", tileSideLength);
132 - RMLog(@"Supported zoom range: %d - %d", minZoom, maxZoom); 130 + RMLog(@"Supported zoom range: %.0f - %.0f", minZoom, maxZoom);
133 RMLog(@"Coverage area: (%2.6f,%2.6f) x (%2.6f,%2.6f)", 131 RMLog(@"Coverage area: (%2.6f,%2.6f) x (%2.6f,%2.6f)",
134 topLeft.latitude, 132 topLeft.latitude,
135 topLeft.longitude, 133 topLeft.longitude,
@@ -180,19 +178,14 @@ @@ -180,19 +178,14 @@
180 return maxZoom; 178 return maxZoom;
181 } 179 }
182 180
183 --(void) setMinZoom:(NSUInteger)aMinZoom  
184 -{  
185 - [tileProjection setMinZoom:aMinZoom];  
186 -}  
187 -  
188 --(void) setMaxZoom:(NSUInteger)aMaxZoom 181 +-(void) setMinZoom:(NSUInteger) aMinZoom
189 { 182 {
190 - [tileProjection setMaxZoom:aMaxZoom]; 183 + minZoom = aMinZoom;
191 } 184 }
192 185
193 --(RMSphericalTrapezium) latitudeLongitudeBoundingBox; 186 +-(void) setMaxZoom:(NSUInteger) aMaxZoom
194 { 187 {
195 - return kDefaultLatLonBoundingBox; 188 + maxZoom = aMaxZoom;
196 } 189 }
197 190
198 -(NSString*) tileURL: (RMTile) tile { 191 -(NSString*) tileURL: (RMTile) tile {
@@ -220,6 +213,21 @@ @@ -220,6 +213,21 @@
220 return [RMProjection googleProjection]; 213 return [RMProjection googleProjection];
221 } 214 }
222 215
  216 +-(RMSphericalTrapezium) latitudeLongitudeBoundingBox
  217 +{
  218 + CLLocationCoordinate2D southwest, northeast;
  219 + southwest.latitude = bottomRight.latitude;
  220 + southwest.longitude = topLeft.longitude;
  221 + northeast.latitude = topLeft.latitude;
  222 + northeast.longitude = bottomRight.longitude;
  223 +
  224 + RMSphericalTrapezium bbox;
  225 + bbox.southwest = southwest;
  226 + bbox.northeast = northeast;
  227 +
  228 + return bbox;
  229 +}
  230 +
223 -(void) didReceiveMemoryWarning { 231 -(void) didReceiveMemoryWarning {
224 LogMethod(); 232 LogMethod();
225 } 233 }
@@ -42,14 +42,16 @@ @@ -42,14 +42,16 @@
42 if (self != nil) { 42 if (self != nil) {
43 // get the unique key for the tile 43 // get the unique key for the tile
44 NSNumber* key = [NSNumber numberWithLongLong:RMTileKey(_tile)]; 44 NSNumber* key = [NSNumber numberWithLongLong:RMTileKey(_tile)];
45 - RMLog(@"fetching tile %@ (y:%d, x:%d)@%d", key, _tile.y, _tile.x, _tile.zoom); 45 +// RMLog(@"fetching tile %@ (y:%d, x:%d)@%d", key, _tile.y, _tile.x, _tile.zoom);
46 46
47 // fetch the image from the db 47 // fetch the image from the db
48 FMResultSet* rs = [db executeQuery:@"select image from tiles where tilekey = ?", key]; 48 FMResultSet* rs = [db executeQuery:@"select image from tiles where tilekey = ?", key];
49 FMDBErrorCheck(db); 49 FMDBErrorCheck(db);
50 if ([rs next]) { 50 if ([rs next]) {
51 [self updateImageUsingImage:[[[UIImage alloc] initWithData:[rs dataForColumn:@"image"]] autorelease]]; 51 [self updateImageUsingImage:[[[UIImage alloc] initWithData:[rs dataForColumn:@"image"]] autorelease]];
52 - } 52 + } else {
  53 + [self updateImageUsingImage:[UIImage imageNamed:@"nodata.png"]];
  54 + }
53 [rs close]; 55 [rs close];
54 } 56 }
55 return self; 57 return self;
@@ -48,6 +48,4 @@ @@ -48,6 +48,4 @@
48 -(void) setCapacity: (NSUInteger) theCapacity; 48 -(void) setCapacity: (NSUInteger) theCapacity;
49 -(void) setMinimalPurge: (NSUInteger) thePurgeMinimum; 49 -(void) setMinimalPurge: (NSUInteger) thePurgeMinimum;
50 50
51 --(void) purgeTilesFromBefore: (NSDate*) date;  
52 -  
53 @end 51 @end
@@ -70,6 +70,10 @@ @@ -70,6 +70,10 @@
70 70
71 self.databasePath = path; 71 self.databasePath = path;
72 dao = [[RMTileCacheDAO alloc] initWithDatabase:path]; 72 dao = [[RMTileCacheDAO alloc] initWithDatabase:path];
  73 + if (!dao) {
  74 + [[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
  75 + dao = [[RMTileCacheDAO alloc] initWithDatabase:path];
  76 + }
73 77
74 if (dao == nil) 78 if (dao == nil)
75 return nil; 79 return nil;
@@ -79,7 +83,7 @@ @@ -79,7 +83,7 @@
79 83
80 -(id) initWithTileSource: (id<RMTileSource>) source usingCacheDir: (BOOL) useCacheDir 84 -(id) initWithTileSource: (id<RMTileSource>) source usingCacheDir: (BOOL) useCacheDir
81 { 85 {
82 - return [self initWithDatabase:[RMDatabaseCache dbPathForTileSource:source usingCacheDir: useCacheDir]]; 86 + return [self initWithDatabase:[RMDatabaseCache dbPathForTileSource:source usingCacheDir:useCacheDir]];
83 } 87 }
84 88
85 -(void) dealloc 89 -(void) dealloc
@@ -119,14 +123,11 @@ @@ -119,14 +123,11 @@
119 /// \bug magic string literals 123 /// \bug magic string literals
120 RMTileImage *image = (RMTileImage*)[notification object]; 124 RMTileImage *image = (RMTileImage*)[notification object];
121 125
122 - @synchronized (self) {  
123 -  
124 - if (capacity != 0) {  
125 - NSUInteger tilesInDb = [dao count];  
126 - if (capacity <= tilesInDb) {  
127 - [dao purgeTiles: MAX(minimalPurge, 1+tilesInDb-capacity)];  
128 - }  
129 - } 126 + if (capacity != 0) {
  127 + NSUInteger tilesInDb = [dao count];
  128 + if (capacity <= tilesInDb) {
  129 + [dao purgeTiles: MAX(minimalPurge, 1+tilesInDb-capacity)];
  130 + }
130 131
131 [dao addData:data LastUsed:[image lastUsedTime] ForTile:RMTileKey([image tile])]; 132 [dao addData:data LastUsed:[image lastUsedTime] ForTile:RMTileKey([image tile])];
132 } 133 }
@@ -145,45 +146,27 @@ @@ -145,45 +146,27 @@
145 146
146 NSData *data = nil; 147 NSData *data = nil;
147 148
148 - @synchronized (self) { 149 + data = [dao dataForTile:RMTileKey(tile)];
  150 + if (data == nil)
  151 + return nil;
149 152
150 - data = [dao dataForTile:RMTileKey(tile)];  
151 - if (data == nil)  
152 - return nil;  
153 -  
154 - if (capacity != 0 && purgeStrategy == RMCachePurgeStrategyLRU) {  
155 - [dao touchTile: RMTileKey(tile) withDate: [NSDate date]];  
156 - }  
157 -  
158 - } 153 + if (capacity != 0 && purgeStrategy == RMCachePurgeStrategyLRU) {
  154 + [dao touchTile: RMTileKey(tile) withDate: [NSDate date]];
  155 + }
159 156
160 RMTileImage *image = [RMTileImage imageForTile:tile withData:data]; 157 RMTileImage *image = [RMTileImage imageForTile:tile withData:data];
161 // RMLog(@"DB cache hit for tile %d %d %d", tile.x, tile.y, tile.zoom); 158 // RMLog(@"DB cache hit for tile %d %d %d", tile.x, tile.y, tile.zoom);
162 return image; 159 return image;
163 } 160 }
164 161
165 --(void) purgeTilesFromBefore: (NSDate*) date  
166 -{  
167 - @synchronized(self)  
168 - {  
169 - [dao purgeTilesFromBefore:date];  
170 - }  
171 -}  
172 -  
173 -(void)didReceiveMemoryWarning 162 -(void)didReceiveMemoryWarning
174 { 163 {
175 - @synchronized(self)  
176 - {  
177 - [dao didReceiveMemoryWarning];  
178 - } 164 + [dao didReceiveMemoryWarning];
179 } 165 }
180 166
181 -(void) removeAllCachedImages 167 -(void) removeAllCachedImages
182 { 168 {
183 - @synchronized(self)  
184 - {  
185 - [dao removeAllCachedImages];  
186 - } 169 + [dao removeAllCachedImages];
187 } 170 }
188 171
189 @end 172 @end
@@ -25,6 +25,9 @@ @@ -25,6 +25,9 @@
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 +#ifndef _FOUNDATION_H_
  29 +#define _FOUNDATION_H_
  30 +
28 /*! \struct RMProjectedPoint 31 /*! \struct RMProjectedPoint
29 \brief coordinates, in projected meters, paralleling CGPoint */ 32 \brief coordinates, in projected meters, paralleling CGPoint */
30 typedef struct { 33 typedef struct {
@@ -52,3 +55,4 @@ RMProjectedRect RMTranslateProjectedRectBy (RMProjectedRect rect, RMProjected @@ -52,3 +55,4 @@ RMProjectedRect RMTranslateProjectedRectBy (RMProjectedRect rect, RMProjected
52 RMProjectedPoint RMMakeProjectedPoint (double easting, double northing); 55 RMProjectedPoint RMMakeProjectedPoint (double easting, double northing);
53 RMProjectedRect RMMakeProjectedRect (double easting, double northing, double width, double height); 56 RMProjectedRect RMMakeProjectedRect (double easting, double northing, double width, double height);
54 57
  58 +#endif
@@ -116,6 +116,7 @@ enum { @@ -116,6 +116,7 @@ enum {
116 float maxZoom; 116 float maxZoom;
117 117
118 float screenScale; 118 float screenScale;
  119 + RMProjectedRect tileSourceProjectedBounds;
119 120
120 id<RMTilesUpdateDelegate> tilesUpdateDelegate; 121 id<RMTilesUpdateDelegate> tilesUpdateDelegate;
121 } 122 }
@@ -129,7 +130,8 @@ enum { @@ -129,7 +130,8 @@ enum {
129 /// zoom level is clamped to range (minZoom, maxZoom) 130 /// zoom level is clamped to range (minZoom, maxZoom)
130 @property (readwrite) float zoom; 131 @property (readwrite) float zoom;
131 132
132 -@property (nonatomic, readwrite) float minZoom, maxZoom; 133 +@property (nonatomic, readwrite) float minZoom;
  134 +@property (nonatomic, readwrite) float maxZoom;
133 135
134 @property (nonatomic, assign) float screenScale; 136 @property (nonatomic, assign) float screenScale;
135 137
@@ -182,6 +184,8 @@ enum { @@ -182,6 +184,8 @@ enum {
182 - (void)handleMemoryWarningNotification:(NSNotification *)notification; 184 - (void)handleMemoryWarningNotification:(NSNotification *)notification;
183 - (void)didReceiveMemoryWarning; 185 - (void)didReceiveMemoryWarning;
184 186
  187 +- (BOOL) tileSourceBoundsContainProjectedPoint:(RMProjectedPoint) point;
  188 +
185 - (void)moveToLatLong: (CLLocationCoordinate2D)latlong; 189 - (void)moveToLatLong: (CLLocationCoordinate2D)latlong;
186 - (void)moveToProjectedPoint: (RMProjectedPoint)aPoint; 190 - (void)moveToProjectedPoint: (RMProjectedPoint)aPoint;
187 191
@@ -223,6 +227,7 @@ enum { @@ -223,6 +227,7 @@ enum {
223 - (RMSphericalTrapezium) latitudeLongitudeBoundingBoxForScreen; 227 - (RMSphericalTrapezium) latitudeLongitudeBoundingBoxForScreen;
224 /// returns the smallest bounding box containing a rectangular region of the screen 228 /// returns the smallest bounding box containing a rectangular region of the screen
225 - (RMSphericalTrapezium) latitudeLongitudeBoundingBoxFor:(CGRect) rect; 229 - (RMSphericalTrapezium) latitudeLongitudeBoundingBoxFor:(CGRect) rect;
  230 +- (BOOL) projectedBounds:(RMProjectedRect)bounds containsPoint:(RMProjectedPoint)point;
226 231
227 - (void)setRotation:(float)angle; 232 - (void)setRotation:(float)angle;
228 233
@@ -65,6 +65,7 @@ @@ -65,6 +65,7 @@
65 @synthesize maxZoom; 65 @synthesize maxZoom;
66 @synthesize screenScale; 66 @synthesize screenScale;
67 @synthesize markerManager; 67 @synthesize markerManager;
  68 +@synthesize imagesOnScreen;
68 69
69 #pragma mark --- begin constants ---- 70 #pragma mark --- begin constants ----
70 #define kZoomAnimationStepTime 0.03f 71 #define kZoomAnimationStepTime 0.03f
@@ -121,7 +122,7 @@ @@ -121,7 +122,7 @@
121 return nil; 122 return nil;
122 123
123 NSAssert1([newView isKindOfClass:[RMMapView class]], @"view %@ must be a subclass of RMMapView", newView); 124 NSAssert1([newView isKindOfClass:[RMMapView class]], @"view %@ must be a subclass of RMMapView", newView);
124 - [(RMMapView *)newView setContents:self]; 125 +// [(RMMapView *)newView setContents:self];
125 126
126 tileSource = nil; 127 tileSource = nil;
127 projection = nil; 128 projection = nil;
@@ -141,10 +142,7 @@ @@ -141,10 +142,7 @@
141 mercatorToScreenProjection = [[RMMercatorToScreenProjection alloc] initFromProjection:[newTilesource projection] ToScreenBounds:[newView bounds]]; 142 mercatorToScreenProjection = [[RMMercatorToScreenProjection alloc] initFromProjection:[newTilesource projection] ToScreenBounds:[newView bounds]];
142 143
143 layer = [[newView layer] retain]; 144 layer = [[newView layer] retain];
144 -  
145 - [self setMinZoom:minZoomLevel];  
146 - [self setMaxZoom:maxZoomLevel];  
147 - 145 +
148 [self setTileSource:newTilesource]; 146 [self setTileSource:newTilesource];
149 [self setRenderer: [[[RMCoreAnimationRenderer alloc] initWithContent:self] autorelease]]; 147 [self setRenderer: [[[RMCoreAnimationRenderer alloc] initWithContent:self] autorelease]];
150 148
@@ -154,6 +152,8 @@ @@ -154,6 +152,8 @@
154 tileLoader = [[RMTileLoader alloc] initWithContent:self]; 152 tileLoader = [[RMTileLoader alloc] initWithContent:self];
155 [tileLoader setSuppressLoading:YES]; 153 [tileLoader setSuppressLoading:YES];
156 154
  155 + [self setMinZoom:minZoomLevel];
  156 + [self setMaxZoom:maxZoomLevel];
157 [self setZoom:initialZoomLevel]; 157 [self setZoom:initialZoomLevel];
158 158
159 [self moveToLatLong:initialCenter]; 159 [self moveToLatLong:initialCenter];
@@ -178,7 +178,7 @@ @@ -178,7 +178,7 @@
178 object:nil]; 178 object:nil];
179 179
180 180
181 - RMLog(@"Map contents initialised. view: %@ tileSource %@ renderer %@", newView, tileSource, renderer); 181 + RMLog(@"Map contents initialised. view: %@ tileSource %@ renderer %@ minZoom:%.0f maxZoom:%.0f", newView, tileSource, renderer, [self minZoom], [self maxZoom]);
182 return self; 182 return self;
183 } 183 }
184 184
@@ -312,6 +312,87 @@ @@ -312,6 +312,87 @@
312 [tileSource didReceiveMemoryWarning]; 312 [tileSource didReceiveMemoryWarning];
313 } 313 }
314 314
  315 +#pragma mark Tile Source Bounds
  316 +
  317 +- (BOOL) projectedBounds:(RMProjectedRect)bounds containsPoint:(RMProjectedPoint)point {
  318 + // NSLog(@"%f %f %f %f point %f %f", bounds.origin.easting, bounds.origin.northing,
  319 + // bounds.size.width, bounds.size.height, point.easting, point.northing);
  320 + if (bounds.origin.easting > point.easting ||
  321 + bounds.origin.easting + bounds.size.width < point.easting ||
  322 + bounds.origin.northing > point.northing ||
  323 + bounds.origin.northing + bounds.size.height < point.northing) {
  324 + return NO;
  325 + }
  326 + return YES;
  327 +}
  328 +
  329 +- (RMProjectedRect) projectedRectFromLatLonBounds:(RMSphericalTrapezium) trap {
  330 + // RMLog(@"southwest={%f,%f}, northeast={%f,%f}", trap.southwest.longitude, trap.southwest.latitude, trap.northeast.longitude, trap.northeast.latitude);
  331 +
  332 + CLLocationCoordinate2D ne = trap.northeast;
  333 + CLLocationCoordinate2D sw = trap.southwest;
  334 + float pixelBuffer = kZoomRectPixelBuffer;
  335 + CLLocationCoordinate2D midpoint = {
  336 + .latitude = (ne.latitude + sw.latitude) / 2,
  337 + .longitude = (ne.longitude + sw.longitude) / 2
  338 + };
  339 + RMProjectedPoint myOrigin = [projection latLongToPoint:midpoint];
  340 + RMProjectedPoint nePoint = [projection latLongToPoint:ne];
  341 + RMProjectedPoint swPoint = [projection latLongToPoint:sw];
  342 + RMProjectedPoint myPoint = {.easting = nePoint.easting - swPoint.easting, .northing = nePoint.northing - swPoint.northing};
  343 + //Create the new zoom layout
  344 + RMProjectedRect zoomRect;
  345 + //Default is with scale = 2.0 mercators/pixel
  346 + zoomRect.size.width = [self screenBounds].size.width * 2.0;
  347 + zoomRect.size.height = [self screenBounds].size.height * 2.0;
  348 + if((myPoint.easting / ([self screenBounds].size.width)) < (myPoint.northing / ([self screenBounds].size.height)))
  349 + {
  350 + if((myPoint.northing / ([self screenBounds].size.height - pixelBuffer)) > 1)
  351 + {
  352 + zoomRect.size.width = [self screenBounds].size.width * (myPoint.northing / ([self screenBounds].size.height - pixelBuffer));
  353 + zoomRect.size.height = [self screenBounds].size.height * (myPoint.northing / ([self screenBounds].size.height - pixelBuffer));
  354 + }
  355 + }
  356 + else
  357 + {
  358 + if((myPoint.easting / ([self screenBounds].size.width - pixelBuffer)) > 1)
  359 + {
  360 + zoomRect.size.width = [self screenBounds].size.width * (myPoint.easting / ([self screenBounds].size.width - pixelBuffer));
  361 + zoomRect.size.height = [self screenBounds].size.height * (myPoint.easting / ([self screenBounds].size.width - pixelBuffer));
  362 + }
  363 + }
  364 + myOrigin.easting = myOrigin.easting - (zoomRect.size.width / 2);
  365 + myOrigin.northing = myOrigin.northing - (zoomRect.size.height / 2);
  366 +
  367 + RMLog(@"Origin is calculated at: %f, %f", [projection pointToLatLong:myOrigin].latitude, [projection pointToLatLong:myOrigin].longitude);
  368 + /*It gets all messed up if our origin is lower than the lowest place on the map, so we check.
  369 + if(myOrigin.northing < -19971868.880409)
  370 + {
  371 + myOrigin.northing = -19971868.880409;
  372 + }*/
  373 +
  374 + zoomRect.origin = myOrigin;
  375 +
  376 + // RMLog(@"Origin: x=%f, y=%f, w=%f, h=%f", zoomRect.origin.easting, zoomRect.origin.northing, zoomRect.size.width, zoomRect.size.height);
  377 +
  378 + return zoomRect;
  379 +
  380 +}
  381 +
  382 +- (BOOL) tileSourceBoundsContainProjectedPoint:(RMProjectedPoint) point {
  383 + RMSphericalTrapezium bounds = [self.tileSource latitudeLongitudeBoundingBox];
  384 + if (bounds.northeast.latitude == 90 && bounds.northeast.longitude == 180 &&
  385 + bounds.southwest.latitude == -90 && bounds.southwest.longitude == -180) {
  386 + return YES;
  387 + }
  388 +// RMLog(@"tileSourceProjectedBounds: x=%f, y=%f, w=%f, h=%f, point: x=%f, y=%f, isInside: %d", tileSourceProjectedBounds.origin.easting, tileSourceProjectedBounds.origin.northing, tileSourceProjectedBounds.size.width, tileSourceProjectedBounds.size.height, point.easting, point.northing, [self projectedBounds:tileSourceProjectedBounds containsPoint:point]);
  389 + return [self projectedBounds:tileSourceProjectedBounds containsPoint:point];
  390 +}
  391 +
  392 +- (BOOL) tileSourceBoundsContainScreenPoint:(CGPoint) point {
  393 + RMProjectedPoint projPoint = [mercatorToScreenProjection projectScreenPointToXY:point];
  394 + return [self tileSourceBoundsContainProjectedPoint:projPoint];
  395 +}
315 396
316 #pragma mark Forwarded Events 397 #pragma mark Forwarded Events
317 398
@@ -320,17 +401,30 @@ @@ -320,17 +401,30 @@
320 RMProjectedPoint aPoint = [[self projection] latLongToPoint:latlong]; 401 RMProjectedPoint aPoint = [[self projection] latLongToPoint:latlong];
321 [self moveToProjectedPoint: aPoint]; 402 [self moveToProjectedPoint: aPoint];
322 } 403 }
  404 +
323 - (void)moveToProjectedPoint: (RMProjectedPoint)aPoint 405 - (void)moveToProjectedPoint: (RMProjectedPoint)aPoint
324 { 406 {
  407 + if (![self tileSourceBoundsContainProjectedPoint:aPoint]) {
  408 + return;
  409 + }
  410 +
325 [mercatorToScreenProjection setProjectedCenter:aPoint]; 411 [mercatorToScreenProjection setProjectedCenter:aPoint];
326 [overlay correctPositionOfAllSublayers]; 412 [overlay correctPositionOfAllSublayers];
327 [tileLoader reload]; 413 [tileLoader reload];
328 [renderer setNeedsDisplay]; 414 [renderer setNeedsDisplay];
329 - [overlay setNeedsDisplay]; 415 + [overlay setNeedsDisplay];
330 } 416 }
331 417
332 - (void)moveBy: (CGSize) delta 418 - (void)moveBy: (CGSize) delta
333 { 419 {
  420 + RMProjectedPoint projCenter = [mercatorToScreenProjection projectedCenter];
  421 + RMProjectedSize XYDelta = [mercatorToScreenProjection projectScreenSizeToXY:delta];
  422 + projCenter.easting = projCenter.easting - XYDelta.width;
  423 + projCenter.northing = projCenter.northing - XYDelta.height;
  424 + if (![self tileSourceBoundsContainProjectedPoint:projCenter]) {
  425 + return;
  426 + }
  427 +
334 [mercatorToScreenProjection moveScreenBy:delta]; 428 [mercatorToScreenProjection moveScreenBy:delta];
335 [imagesOnScreen moveBy:delta]; 429 [imagesOnScreen moveBy:delta];
336 [tileLoader moveBy:delta]; 430 [tileLoader moveBy:delta];
@@ -425,6 +519,8 @@ @@ -425,6 +519,8 @@
425 /// \bug this is a no-op, not a clamp, if new zoom would be outside of minzoom/maxzoom range 519 /// \bug this is a no-op, not a clamp, if new zoom would be outside of minzoom/maxzoom range
426 - (void)zoomByFactor: (float) zoomFactor near:(CGPoint) pivot 520 - (void)zoomByFactor: (float) zoomFactor near:(CGPoint) pivot
427 { 521 {
  522 + if (![self tileSourceBoundsContainScreenPoint:pivot]) return;
  523 +
428 //[self zoomByFactor:zoomFactor near:pivot animated:NO]; 524 //[self zoomByFactor:zoomFactor near:pivot animated:NO];
429 525
430 zoomFactor = [self adjustZoomForBoundingMask:zoomFactor]; 526 zoomFactor = [self adjustZoomForBoundingMask:zoomFactor];
@@ -440,6 +536,7 @@ @@ -440,6 +536,7 @@
440 [imagesOnScreen zoomByFactor:zoomFactor near:pivot]; 536 [imagesOnScreen zoomByFactor:zoomFactor near:pivot];
441 [tileLoader zoomByFactor:zoomFactor near:pivot]; 537 [tileLoader zoomByFactor:zoomFactor near:pivot];
442 [overlay zoomByFactor:zoomFactor near:pivot]; 538 [overlay zoomByFactor:zoomFactor near:pivot];
  539 + [overlay correctPositionOfAllSublayers];
443 [renderer setNeedsDisplay]; 540 [renderer setNeedsDisplay];
444 } 541 }
445 } 542 }
@@ -472,6 +569,8 @@ @@ -472,6 +569,8 @@
472 569
473 - (void)zoomByFactor: (float) zoomFactor near:(CGPoint) pivot animated:(BOOL) animated withCallback:(id<RMMapContentsAnimationCallback>)callback 570 - (void)zoomByFactor: (float) zoomFactor near:(CGPoint) pivot animated:(BOOL) animated withCallback:(id<RMMapContentsAnimationCallback>)callback
474 { 571 {
  572 + if (![self tileSourceBoundsContainScreenPoint:pivot]) return;
  573 +
475 zoomFactor = [self adjustZoomForBoundingMask:zoomFactor]; 574 zoomFactor = [self adjustZoomForBoundingMask:zoomFactor];
476 float zoomDelta = log2f(zoomFactor); 575 float zoomDelta = log2f(zoomFactor);
477 float targetZoom = zoomDelta + [self zoom]; 576 float targetZoom = zoomDelta + [self zoom];
@@ -519,6 +618,7 @@ @@ -519,6 +618,7 @@
519 [imagesOnScreen zoomByFactor:zoomFactor near:pivot]; 618 [imagesOnScreen zoomByFactor:zoomFactor near:pivot];
520 [tileLoader zoomByFactor:zoomFactor near:pivot]; 619 [tileLoader zoomByFactor:zoomFactor near:pivot];
521 [overlay zoomByFactor:zoomFactor near:pivot]; 620 [overlay zoomByFactor:zoomFactor near:pivot];
  621 + [overlay correctPositionOfAllSublayers];
522 [renderer setNeedsDisplay]; 622 [renderer setNeedsDisplay];
523 } 623 }
524 } 624 }
@@ -613,28 +713,45 @@ @@ -613,28 +713,45 @@
613 713
614 #pragma mark Properties 714 #pragma mark Properties
615 715
  716 +static NSMutableDictionary *cachedTilesources = nil;
  717 +
616 - (void) setTileSource: (id<RMTileSource>)newTileSource 718 - (void) setTileSource: (id<RMTileSource>)newTileSource
617 { 719 {
618 if (tileSource == newTileSource) 720 if (tileSource == newTileSource)
619 return; 721 return;
620 -  
621 - RMCachedTileSource *newCachedTileSource = [RMCachedTileSource cachedTileSourceWithSource:newTileSource];  
622 722
623 - newCachedTileSource = [newCachedTileSource retain];  
624 - [tileSource release];  
625 - tileSource = newCachedTileSource; 723 + if (!cachedTilesources) cachedTilesources = [NSMutableDictionary new];
  724 + if ([cachedTilesources count] > 3) [cachedTilesources removeAllObjects];
626 725
627 - NSAssert(([tileSource minZoom] - minZoom) <= 1.0, @"Graphics & memory are overly taxed if [contents minZoom] is more than 1.5 smaller than [tileSource minZoom]");  
628 - 726 + RMCachedTileSource *newCachedTileSource = [cachedTilesources objectForKey:[newTileSource uniqueTilecacheKey]];
  727 + if (!newCachedTileSource) {
  728 + newCachedTileSource = [RMCachedTileSource cachedTileSourceWithSource:newTileSource];
  729 + minZoom = newCachedTileSource.minZoom;
  730 + maxZoom = newCachedTileSource.maxZoom + 1;
  731 +
  732 + if ([newTileSource uniqueTilecacheKey])
  733 + [cachedTilesources setObject:newCachedTileSource forKey:[newTileSource uniqueTilecacheKey]];
  734 + }
  735 +
  736 + [self setZoom:[self zoom]]; // setZoom clamps zoom level to min/max limits
  737 +
  738 + [tileSource autorelease];
  739 + tileSource = [newCachedTileSource retain];
  740 +
  741 + if (([tileSource minZoom] - minZoom) <= 1.0) {
  742 + RMLog(@"Graphics & memory are overly taxed if [contents minZoom] is more than 1.5 smaller than [tileSource minZoom]");
  743 + }
  744 +
629 [projection release]; 745 [projection release];
630 projection = [[tileSource projection] retain]; 746 projection = [[tileSource projection] retain];
631 - 747 +
632 [mercatorToTileProjection release]; 748 [mercatorToTileProjection release];
633 mercatorToTileProjection = [[tileSource mercatorToTileProjection] retain]; 749 mercatorToTileProjection = [[tileSource mercatorToTileProjection] retain];
634 - 750 + tileSourceProjectedBounds = (RMProjectedRect)[self projectedRectFromLatLonBounds:[tileSource latitudeLongitudeBoundingBox]];
  751 +
635 [imagesOnScreen setTileSource:tileSource]; 752 [imagesOnScreen setTileSource:tileSource];
636 753
637 - [tileLoader reset]; 754 + [tileLoader reset];
638 [tileLoader reload]; 755 [tileLoader reload];
639 } 756 }
640 757
@@ -125,21 +125,24 @@ typedef struct { @@ -125,21 +125,24 @@ typedef struct {
125 id<RMMapViewDelegate> delegate; 125 id<RMMapViewDelegate> delegate;
126 BOOL enableDragging; 126 BOOL enableDragging;
127 BOOL enableZoom; 127 BOOL enableZoom;
128 - BOOL enableRotate; 128 + BOOL enableRotate;
129 RMGestureDetails lastGesture; 129 RMGestureDetails lastGesture;
130 float decelerationFactor; 130 float decelerationFactor;
131 BOOL deceleration; 131 BOOL deceleration;
132 - CGFloat rotation; 132 + CGFloat rotation;
133 133
134 @private 134 @private
135 BOOL _delegateHasBeforeMapMove; 135 BOOL _delegateHasBeforeMapMove;
136 BOOL _delegateHasAfterMapMove; 136 BOOL _delegateHasAfterMapMove;
  137 + BOOL _delegateHasAfterMapMoveDeceleration;
137 BOOL _delegateHasBeforeMapZoomByFactor; 138 BOOL _delegateHasBeforeMapZoomByFactor;
138 BOOL _delegateHasAfterMapZoomByFactor; 139 BOOL _delegateHasAfterMapZoomByFactor;
139 BOOL _delegateHasBeforeMapRotate; 140 BOOL _delegateHasBeforeMapRotate;
140 BOOL _delegateHasAfterMapRotate; 141 BOOL _delegateHasAfterMapRotate;
141 BOOL _delegateHasDoubleTapOnMap; 142 BOOL _delegateHasDoubleTapOnMap;
  143 + BOOL _delegateHasDoubleTapTwoFingersOnMap;
142 BOOL _delegateHasSingleTapOnMap; 144 BOOL _delegateHasSingleTapOnMap;
  145 + BOOL _delegateHasLongSingleTapOnMap;
143 BOOL _delegateHasTapOnMarker; 146 BOOL _delegateHasTapOnMarker;
144 BOOL _delegateHasTapOnLabelForMarker; 147 BOOL _delegateHasTapOnLabelForMarker;
145 BOOL _delegateHasAfterMapTouch; 148 BOOL _delegateHasAfterMapTouch;
@@ -150,6 +153,8 @@ typedef struct { @@ -150,6 +153,8 @@ typedef struct {
150 NSTimer *_decelerationTimer; 153 NSTimer *_decelerationTimer;
151 CGSize _decelerationDelta; 154 CGSize _decelerationDelta;
152 155
  156 + CGPoint _longPressPosition;
  157 +
153 BOOL _constrainMovement; 158 BOOL _constrainMovement;
154 RMProjectedPoint NEconstraint, SWconstraint; 159 RMProjectedPoint NEconstraint, SWconstraint;
155 160
@@ -175,8 +180,6 @@ typedef struct { @@ -175,8 +180,6 @@ typedef struct {
175 180
176 @property (readonly) CGFloat rotation; 181 @property (readonly) CGFloat rotation;
177 182
178 -- (id)initWithFrame:(CGRect)frame WithLocation:(CLLocationCoordinate2D)latlong;  
179 -  
180 /// recenter the map on #latlong, expressed as CLLocationCoordinate2D (latitude/longitude) 183 /// recenter the map on #latlong, expressed as CLLocationCoordinate2D (latitude/longitude)
181 - (void)moveToLatLong: (CLLocationCoordinate2D)latlong; 184 - (void)moveToLatLong: (CLLocationCoordinate2D)latlong;
182 /// recenter the map on #aPoint, expressed in projected meters 185 /// recenter the map on #aPoint, expressed in projected meters
@@ -193,5 +196,4 @@ typedef struct { @@ -193,5 +196,4 @@ typedef struct {
193 196
194 - (void)setRotation:(CGFloat)angle; 197 - (void)setRotation:(CGFloat)angle;
195 198
196 -  
197 @end 199 @end
@@ -44,7 +44,6 @@ @@ -44,7 +44,6 @@
44 @end 44 @end
45 45
46 @implementation RMMapView 46 @implementation RMMapView
47 -@synthesize contents;  
48 47
49 @synthesize decelerationFactor; 48 @synthesize decelerationFactor;
50 @synthesize deceleration; 49 @synthesize deceleration;
@@ -62,7 +61,7 @@ @@ -62,7 +61,7 @@
62 61
63 - (RMMarkerManager*)markerManager 62 - (RMMarkerManager*)markerManager
64 { 63 {
65 - return self.contents.markerManager; 64 + return contents.markerManager;
66 } 65 }
67 66
68 -(void) performInitialSetup 67 -(void) performInitialSetup
@@ -91,40 +90,28 @@ @@ -91,40 +90,28 @@
91 { 90 {
92 LogMethod(); 91 LogMethod();
93 if (self = [super initWithFrame:frame]) { 92 if (self = [super initWithFrame:frame]) {
  93 + contents = nil;
94 [self performInitialSetup]; 94 [self performInitialSetup];
95 } 95 }
96 return self; 96 return self;
97 } 97 }
98 98
99 -/// \deprecated Deprecated any time after 0.5.  
100 -- (id)initWithFrame:(CGRect)frame WithLocation:(CLLocationCoordinate2D)latlon  
101 -{  
102 - WarnDeprecated();  
103 - LogMethod();  
104 - if (self = [super initWithFrame:frame]) {  
105 - [self performInitialSetup];  
106 - }  
107 - [self moveToLatLong:latlon];  
108 - return self;  
109 -}  
110 -  
111 //=========================================================== 99 //===========================================================
112 // contents 100 // contents
113 //=========================================================== 101 //===========================================================
114 - (RMMapContents *)contents 102 - (RMMapContents *)contents
115 { 103 {
116 if (!_contentsIsSet) { 104 if (!_contentsIsSet) {
117 - RMMapContents *newContents = [[RMMapContents alloc] initWithView:self];  
118 - self.contents = newContents;  
119 - [newContents release]; 105 + contents = [[RMMapContents alloc] initWithView:self];
120 _contentsIsSet = YES; 106 _contentsIsSet = YES;
121 } 107 }
122 return contents; 108 return contents;
123 } 109 }
  110 +
124 - (void)setContents:(RMMapContents *)theContents 111 - (void)setContents:(RMMapContents *)theContents
125 { 112 {
126 if (contents != theContents) { 113 if (contents != theContents) {
127 - [contents release]; 114 + [contents autorelease];
128 contents = [theContents retain]; 115 contents = [theContents retain];
129 _contentsIsSet = YES; 116 _contentsIsSet = YES;
130 [self performInitialSetup]; 117 [self performInitialSetup];
@@ -134,13 +121,15 @@ @@ -134,13 +121,15 @@
134 -(void) dealloc 121 -(void) dealloc
135 { 122 {
136 LogMethod(); 123 LogMethod();
137 - self.contents = nil; 124 + [self setDelegate:nil];
  125 + [self stopDeceleration];
  126 + [contents release]; contents = nil;
138 [super dealloc]; 127 [super dealloc];
139 } 128 }
140 129
141 -(void) drawRect: (CGRect) rect 130 -(void) drawRect: (CGRect) rect
142 { 131 {
143 - [self.contents drawRect:rect]; 132 + [contents drawRect:rect];
144 } 133 }
145 134
146 -(NSString*) description 135 -(NSString*) description
@@ -154,8 +143,8 @@ @@ -154,8 +143,8 @@
154 { 143 {
155 SEL aSelector = [invocation selector]; 144 SEL aSelector = [invocation selector];
156 145
157 - if ([self.contents respondsToSelector:aSelector])  
158 - [invocation invokeWithTarget:self.contents]; 146 + if ([contents respondsToSelector:aSelector])
  147 + [invocation invokeWithTarget:contents];
159 else 148 else
160 [self doesNotRecognizeSelector:aSelector]; 149 [self doesNotRecognizeSelector:aSelector];
161 } 150 }
@@ -165,7 +154,7 @@ @@ -165,7 +154,7 @@
165 if ([super respondsToSelector:aSelector]) 154 if ([super respondsToSelector:aSelector])
166 return [super methodSignatureForSelector:aSelector]; 155 return [super methodSignatureForSelector:aSelector];
167 else 156 else
168 - return [self.contents methodSignatureForSelector:aSelector]; 157 + return [contents methodSignatureForSelector:aSelector];
169 } 158 }
170 159
171 #pragma mark Delegate 160 #pragma mark Delegate
@@ -179,7 +168,8 @@ @@ -179,7 +168,8 @@
179 168
180 _delegateHasBeforeMapMove = [(NSObject*) delegate respondsToSelector: @selector(beforeMapMove:)]; 169 _delegateHasBeforeMapMove = [(NSObject*) delegate respondsToSelector: @selector(beforeMapMove:)];
181 _delegateHasAfterMapMove = [(NSObject*) delegate respondsToSelector: @selector(afterMapMove:)]; 170 _delegateHasAfterMapMove = [(NSObject*) delegate respondsToSelector: @selector(afterMapMove:)];
182 - 171 + _delegateHasAfterMapMoveDeceleration = [(NSObject*) delegate respondsToSelector: @selector(afterMapMoveDeceleration:)];
  172 +
183 _delegateHasBeforeMapZoomByFactor = [(NSObject*) delegate respondsToSelector: @selector(beforeMapZoom: byFactor: near:)]; 173 _delegateHasBeforeMapZoomByFactor = [(NSObject*) delegate respondsToSelector: @selector(beforeMapZoom: byFactor: near:)];
184 _delegateHasAfterMapZoomByFactor = [(NSObject*) delegate respondsToSelector: @selector(afterMapZoom: byFactor: near:)]; 174 _delegateHasAfterMapZoomByFactor = [(NSObject*) delegate respondsToSelector: @selector(afterMapZoom: byFactor: near:)];
185 175
@@ -187,8 +177,10 @@ @@ -187,8 +177,10 @@
187 _delegateHasAfterMapRotate = [(NSObject*) delegate respondsToSelector: @selector(afterMapRotate: toAngle:)]; 177 _delegateHasAfterMapRotate = [(NSObject*) delegate respondsToSelector: @selector(afterMapRotate: toAngle:)];
188 178
189 _delegateHasDoubleTapOnMap = [(NSObject*) delegate respondsToSelector: @selector(doubleTapOnMap:At:)]; 179 _delegateHasDoubleTapOnMap = [(NSObject*) delegate respondsToSelector: @selector(doubleTapOnMap:At:)];
  180 + _delegateHasDoubleTapTwoFingersOnMap = [(NSObject *)delegate respondsToSelector:@selector(doubleTapTwoFingersOnMap:At:)];
190 _delegateHasSingleTapOnMap = [(NSObject*) delegate respondsToSelector: @selector(singleTapOnMap:At:)]; 181 _delegateHasSingleTapOnMap = [(NSObject*) delegate respondsToSelector: @selector(singleTapOnMap:At:)];
191 - 182 + _delegateHasLongSingleTapOnMap = [(NSObject *) delegate respondsToSelector: @selector(longSingleTapOnMap:At:)];
  183 +
192 _delegateHasTapOnMarker = [(NSObject*) delegate respondsToSelector:@selector(tapOnMarker:onMap:)]; 184 _delegateHasTapOnMarker = [(NSObject*) delegate respondsToSelector:@selector(tapOnMarker:onMap:)];
193 _delegateHasTapOnLabelForMarker = [(NSObject*) delegate respondsToSelector:@selector(tapOnLabelForMarker:onMap:)]; 185 _delegateHasTapOnLabelForMarker = [(NSObject*) delegate respondsToSelector:@selector(tapOnLabelForMarker:onMap:)];
194 186
@@ -210,13 +202,14 @@ @@ -210,13 +202,14 @@
210 -(void) moveToProjectedPoint: (RMProjectedPoint) aPoint 202 -(void) moveToProjectedPoint: (RMProjectedPoint) aPoint
211 { 203 {
212 if (_delegateHasBeforeMapMove) [delegate beforeMapMove: self]; 204 if (_delegateHasBeforeMapMove) [delegate beforeMapMove: self];
213 - [self.contents moveToProjectedPoint:aPoint]; 205 + [contents moveToProjectedPoint:aPoint];
214 if (_delegateHasAfterMapMove) [delegate afterMapMove: self]; 206 if (_delegateHasAfterMapMove) [delegate afterMapMove: self];
215 } 207 }
  208 +
216 -(void) moveToLatLong: (CLLocationCoordinate2D) point 209 -(void) moveToLatLong: (CLLocationCoordinate2D) point
217 { 210 {
218 if (_delegateHasBeforeMapMove) [delegate beforeMapMove: self]; 211 if (_delegateHasBeforeMapMove) [delegate beforeMapMove: self];
219 - [self.contents moveToLatLong:point]; 212 + [contents moveToLatLong:point];
220 if (_delegateHasAfterMapMove) [delegate afterMapMove: self]; 213 if (_delegateHasAfterMapMove) [delegate afterMapMove: self];
221 } 214 }
222 215
@@ -267,7 +260,7 @@ @@ -267,7 +260,7 @@
267 } 260 }
268 261
269 if (_delegateHasBeforeMapMove) [delegate beforeMapMove: self]; 262 if (_delegateHasBeforeMapMove) [delegate beforeMapMove: self];
270 - [self.contents moveBy:delta]; 263 + [contents moveBy:delta];
271 if (_delegateHasAfterMapMove) [delegate afterMapMove: self]; 264 if (_delegateHasAfterMapMove) [delegate afterMapMove: self];
272 } 265 }
273 266
@@ -350,11 +343,16 @@ @@ -350,11 +343,16 @@
350 } 343 }
351 344
352 if (_delegateHasBeforeMapZoomByFactor) [delegate beforeMapZoom: self byFactor: zoomFactor near: center]; 345 if (_delegateHasBeforeMapZoomByFactor) [delegate beforeMapZoom: self byFactor: zoomFactor near: center];
353 - [self.contents zoomByFactor:zoomFactor near:center animated:animated withCallback:(animated && _delegateHasAfterMapZoomByFactor)?self:nil]; 346 + [contents zoomByFactor:zoomFactor near:center animated:animated withCallback:(animated && _delegateHasAfterMapZoomByFactor)?self:nil];
354 if (!animated) 347 if (!animated)
355 if (_delegateHasAfterMapZoomByFactor) [delegate afterMapZoom: self byFactor: zoomFactor near: center]; 348 if (_delegateHasAfterMapZoomByFactor) [delegate afterMapZoom: self byFactor: zoomFactor near: center];
356 } 349 }
357 350
  351 +- (void)zoomWithLatLngBoundsNorthEast:(CLLocationCoordinate2D)ne SouthWest:(CLLocationCoordinate2D)sw
  352 +{
  353 + [contents zoomWithLatLngBoundsNorthEast:ne SouthWest:sw];
  354 + [self moveBy:CGSizeZero];
  355 +}
358 356
359 #pragma mark RMMapContentsAnimationCallback methods 357 #pragma mark RMMapContentsAnimationCallback methods
360 358
@@ -450,12 +448,18 @@ @@ -450,12 +448,18 @@
450 [self performSelector:@selector(resumeExpensiveOperations) withObject:nil afterDelay:0.4]; 448 [self performSelector:@selector(resumeExpensiveOperations) withObject:nil afterDelay:0.4];
451 } 449 }
452 450
  451 +- (void)handleLongPress
  452 +{
  453 + if (_delegateHasLongSingleTapOnMap)
  454 + [delegate longSingleTapOnMap:self At:_longPressPosition];
  455 +}
  456 +
453 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 457 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
454 { 458 {
455 UITouch *touch = [[touches allObjects] objectAtIndex:0]; 459 UITouch *touch = [[touches allObjects] objectAtIndex:0];
456 //Check if the touch hit a RMMarker subclass and if so, forward the touch event on 460 //Check if the touch hit a RMMarker subclass and if so, forward the touch event on
457 //so it can be handled there 461 //so it can be handled there
458 - id furthestLayerDown = [self.contents.overlay hitTest:[touch locationInView:self]]; 462 + id furthestLayerDown = [contents.overlay hitTest:[touch locationInView:self]];
459 if ([[furthestLayerDown class]isSubclassOfClass: [RMMarker class]]) { 463 if ([[furthestLayerDown class]isSubclassOfClass: [RMMarker class]]) {
460 if ([furthestLayerDown respondsToSelector:@selector(touchesBegan:withEvent:)]) { 464 if ([furthestLayerDown respondsToSelector:@selector(touchesBegan:withEvent:)]) {
461 [furthestLayerDown performSelector:@selector(touchesBegan:withEvent:) withObject:touches withObject:event]; 465 [furthestLayerDown performSelector:@selector(touchesBegan:withEvent:) withObject:touches withObject:event];
@@ -478,6 +482,16 @@ @@ -478,6 +482,16 @@
478 } 482 }
479 } 483 }
480 484
  485 + _longPressPosition = lastGesture.center;
  486 + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleLongPress) object:nil];
  487 +
  488 + if (lastGesture.numTouches == 1) {
  489 + CALayer* hit = [contents.overlay hitTest:[touch locationInView:self]];
  490 + if (!hit || ![hit isKindOfClass: [RMMarker class]]) {
  491 + [self performSelector:@selector(handleLongPress) withObject:nil afterDelay:0.5];
  492 + }
  493 + }
  494 +
481 [self delayedResumeExpensiveOperations]; 495 [self delayedResumeExpensiveOperations];
482 } 496 }
483 497
@@ -486,9 +500,11 @@ @@ -486,9 +500,11 @@
486 { 500 {
487 UITouch *touch = [[touches allObjects] objectAtIndex:0]; 501 UITouch *touch = [[touches allObjects] objectAtIndex:0];
488 502
  503 + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleLongPress) object:nil];
  504 +
489 //Check if the touch hit a RMMarker subclass and if so, forward the touch event on 505 //Check if the touch hit a RMMarker subclass and if so, forward the touch event on
490 //so it can be handled there 506 //so it can be handled there
491 - id furthestLayerDown = [self.contents.overlay hitTest:[touch locationInView:self]]; 507 + id furthestLayerDown = [contents.overlay hitTest:[touch locationInView:self]];
492 if ([[furthestLayerDown class]isSubclassOfClass: [RMMarker class]]) { 508 if ([[furthestLayerDown class]isSubclassOfClass: [RMMarker class]]) {
493 if ([furthestLayerDown respondsToSelector:@selector(touchesCancelled:withEvent:)]) { 509 if ([furthestLayerDown respondsToSelector:@selector(touchesCancelled:withEvent:)]) {
494 [furthestLayerDown performSelector:@selector(touchesCancelled:withEvent:) withObject:touches withObject:event]; 510 [furthestLayerDown performSelector:@selector(touchesCancelled:withEvent:) withObject:touches withObject:event];
@@ -506,7 +522,7 @@ @@ -506,7 +522,7 @@
506 522
507 //Check if the touch hit a RMMarker subclass and if so, forward the touch event on 523 //Check if the touch hit a RMMarker subclass and if so, forward the touch event on
508 //so it can be handled there 524 //so it can be handled there
509 - id furthestLayerDown = [self.contents.overlay hitTest:[touch locationInView:self]]; 525 + id furthestLayerDown = [contents.overlay hitTest:[touch locationInView:self]];
510 if ([[furthestLayerDown class]isSubclassOfClass: [RMMarker class]]) { 526 if ([[furthestLayerDown class]isSubclassOfClass: [RMMarker class]]) {
511 if ([furthestLayerDown respondsToSelector:@selector(touchesEnded:withEvent:)]) { 527 if ([furthestLayerDown respondsToSelector:@selector(touchesEnded:withEvent:)]) {
512 [furthestLayerDown performSelector:@selector(touchesEnded:withEvent:) withObject:touches withObject:event]; 528 [furthestLayerDown performSelector:@selector(touchesEnded:withEvent:) withObject:touches withObject:event];
@@ -519,15 +535,28 @@ @@ -519,15 +535,28 @@
519 lastGesture = [self gestureDetails:[event allTouches]]; 535 lastGesture = [self gestureDetails:[event allTouches]];
520 536
521 BOOL decelerating = NO; 537 BOOL decelerating = NO;
522 - if (touch.tapCount >= 2) 538 +
  539 + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleLongPress) object:nil];
  540 +
  541 + if (touch.tapCount >= 2)
523 { 542 {
  543 + BOOL twoFingerTap = [touches count] >= 2;
524 if (_delegateHasDoubleTapOnMap) { 544 if (_delegateHasDoubleTapOnMap) {
525 - [delegate doubleTapOnMap: self At: lastGesture.center]; 545 + if (twoFingerTap) {
  546 + if (_delegateHasDoubleTapTwoFingersOnMap) [delegate doubleTapTwoFingersOnMap: self At: lastGesture.center];
  547 + } else {
  548 + if (_delegateHasDoubleTapOnMap) [delegate doubleTapOnMap: self At: lastGesture.center];
  549 + }
526 } else { 550 } else {
527 // Default behaviour matches built in maps.app 551 // Default behaviour matches built in maps.app
528 - float nextZoomFactor = [self.contents nextNativeZoomFactor];  
529 - if (nextZoomFactor != 0)  
530 - [self zoomByFactor:nextZoomFactor near:[touch locationInView:self] animated:YES]; 552 + float nextZoomFactor = 0;
  553 + if (twoFingerTap) {
  554 + nextZoomFactor = [contents prevNativeZoomFactor];
  555 + } else {
  556 + nextZoomFactor = [contents nextNativeZoomFactor];
  557 + }
  558 + if (nextZoomFactor != 0)
  559 + [self zoomByFactor:nextZoomFactor near:[touch locationInView:self] animated:YES];
531 } 560 }
532 } else if (lastTouches == 1 && touch.tapCount != 1) { 561 } else if (lastTouches == 1 && touch.tapCount != 1) {
533 // deceleration 562 // deceleration
@@ -541,19 +570,17 @@ @@ -541,19 +570,17 @@
541 } 570 }
542 } 571 }
543 572
544 -  
545 // If there are no more fingers on the screen, resume any slow operations. 573 // If there are no more fingers on the screen, resume any slow operations.
546 if (lastGesture.numTouches == 0 && !decelerating) 574 if (lastGesture.numTouches == 0 && !decelerating)
547 { 575 {
548 [self delayedResumeExpensiveOperations]; 576 [self delayedResumeExpensiveOperations];
549 } 577 }
550 578
551 -  
552 if (touch.tapCount == 1) 579 if (touch.tapCount == 1)
553 { 580 {
554 if(lastGesture.numTouches == 0) 581 if(lastGesture.numTouches == 0)
555 { 582 {
556 - CALayer* hit = [self.contents.overlay hitTest:[touch locationInView:self]]; 583 + CALayer* hit = [contents.overlay hitTest:[touch locationInView:self]];
557 // RMLog(@"LAYER of type %@",[hit description]); 584 // RMLog(@"LAYER of type %@",[hit description]);
558 585
559 if (hit != nil) { 586 if (hit != nil) {
@@ -579,7 +606,7 @@ @@ -579,7 +606,7 @@
579 } 606 }
580 else if(!enableDragging && (lastGesture.numTouches == 1)) 607 else if(!enableDragging && (lastGesture.numTouches == 1))
581 { 608 {
582 - float prevZoomFactor = [self.contents prevNativeZoomFactor]; 609 + float prevZoomFactor = [contents prevNativeZoomFactor];
583 if (prevZoomFactor != 0) 610 if (prevZoomFactor != 0)
584 [self zoomByFactor:prevZoomFactor near:[touch locationInView:self] animated:YES]; 611 [self zoomByFactor:prevZoomFactor near:[touch locationInView:self] animated:YES];
585 } 612 }
@@ -594,7 +621,7 @@ @@ -594,7 +621,7 @@
594 621
595 //Check if the touch hit a RMMarker subclass and if so, forward the touch event on 622 //Check if the touch hit a RMMarker subclass and if so, forward the touch event on
596 //so it can be handled there 623 //so it can be handled there
597 - id furthestLayerDown = [self.contents.overlay hitTest:[touch locationInView:self]]; 624 + id furthestLayerDown = [contents.overlay hitTest:[touch locationInView:self]];
598 if ([[furthestLayerDown class]isSubclassOfClass: [RMMarker class]]) { 625 if ([[furthestLayerDown class]isSubclassOfClass: [RMMarker class]]) {
599 if ([furthestLayerDown respondsToSelector:@selector(touchesMoved:withEvent:)]) { 626 if ([furthestLayerDown respondsToSelector:@selector(touchesMoved:withEvent:)]) {
600 [furthestLayerDown performSelector:@selector(touchesMoved:withEvent:) withObject:touches withObject:event]; 627 [furthestLayerDown performSelector:@selector(touchesMoved:withEvent:) withObject:touches withObject:event];
@@ -602,7 +629,14 @@ @@ -602,7 +629,14 @@
602 } 629 }
603 } 630 }
604 631
605 - CALayer* hit = [self.contents.overlay hitTest:[touch locationInView:self]]; 632 + RMGestureDetails newGesture = [self gestureDetails:[event allTouches]];
  633 + CGPoint newLongPressPosition = newGesture.center;
  634 + CGFloat dx = newLongPressPosition.x - _longPressPosition.x;
  635 + CGFloat dy = newLongPressPosition.y - _longPressPosition.y;
  636 + if (sqrt(dx*dx + dy*dy) > 5)
  637 + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleLongPress) object:nil];
  638 +
  639 + CALayer* hit = [contents.overlay hitTest:[touch locationInView:self]];
606 // RMLog(@"LAYER of type %@",[hit description]); 640 // RMLog(@"LAYER of type %@",[hit description]);
607 641
608 if (hit != nil) { 642 if (hit != nil) {
@@ -617,9 +651,7 @@ @@ -617,9 +651,7 @@
617 } 651 }
618 } 652 }
619 } 653 }
620 -  
621 - RMGestureDetails newGesture = [self gestureDetails:[event allTouches]];  
622 - 654 +
623 if(enableRotate && (newGesture.numTouches == lastGesture.numTouches)) 655 if(enableRotate && (newGesture.numTouches == lastGesture.numTouches))
624 { 656 {
625 if(newGesture.numTouches == 2) 657 if(newGesture.numTouches == 2)
@@ -665,10 +697,10 @@ @@ -665,10 +697,10 @@
665 _decelerationDelta = delta; 697 _decelerationDelta = delta;
666 if ( !_decelerationTimer ) { 698 if ( !_decelerationTimer ) {
667 _decelerationTimer = [NSTimer scheduledTimerWithTimeInterval:0.01f 699 _decelerationTimer = [NSTimer scheduledTimerWithTimeInterval:0.01f
668 - target:self  
669 - selector:@selector(incrementDeceleration:)  
670 - userInfo:nil  
671 - repeats:YES]; 700 + target:self
  701 + selector:@selector(incrementDeceleration:)
  702 + userInfo:nil
  703 + repeats:YES];
672 } 704 }
673 } 705 }
674 } 706 }
@@ -684,7 +716,7 @@ @@ -684,7 +716,7 @@
684 } 716 }
685 717
686 // avoid calling delegate methods? design call here 718 // avoid calling delegate methods? design call here
687 - [self.contents moveBy:_decelerationDelta]; 719 + [self moveBy:_decelerationDelta];
688 720
689 _decelerationDelta.width *= [self decelerationFactor]; 721 _decelerationDelta.width *= [self decelerationFactor];
690 _decelerationDelta.height *= [self decelerationFactor]; 722 _decelerationDelta.height *= [self decelerationFactor];
@@ -699,6 +731,9 @@ @@ -699,6 +731,9 @@
699 // call delegate methods; design call (see above) 731 // call delegate methods; design call (see above)
700 [self moveBy:CGSizeZero]; 732 [self moveBy:CGSizeZero];
701 } 733 }
  734 +
  735 + if (_delegateHasAfterMapMoveDeceleration)
  736 + [delegate afterMapMoveDeceleration:self];
702 } 737 }
703 738
704 /// Must be called by higher didReceiveMemoryWarning 739 /// Must be called by higher didReceiveMemoryWarning
@@ -37,6 +37,7 @@ @@ -37,6 +37,7 @@
37 37
38 - (void) beforeMapMove: (RMMapView*) map; 38 - (void) beforeMapMove: (RMMapView*) map;
39 - (void) afterMapMove: (RMMapView*) map ; 39 - (void) afterMapMove: (RMMapView*) map ;
  40 +- (void) afterMapMoveDeceleration: (RMMapView*) map;
40 41
41 - (void) beforeMapZoom: (RMMapView*) map byFactor: (float) zoomFactor near:(CGPoint) center; 42 - (void) beforeMapZoom: (RMMapView*) map byFactor: (float) zoomFactor near:(CGPoint) center;
42 - (void) afterMapZoom: (RMMapView*) map byFactor: (float) zoomFactor near:(CGPoint) center; 43 - (void) afterMapZoom: (RMMapView*) map byFactor: (float) zoomFactor near:(CGPoint) center;
@@ -45,7 +46,9 @@ @@ -45,7 +46,9 @@
45 - (void) afterMapRotate: (RMMapView*) map toAngle: (CGFloat) angle; 46 - (void) afterMapRotate: (RMMapView*) map toAngle: (CGFloat) angle;
46 47
47 - (void) doubleTapOnMap: (RMMapView*) map At: (CGPoint) point; 48 - (void) doubleTapOnMap: (RMMapView*) map At: (CGPoint) point;
  49 +- (void) doubleTapTwoFingersOnMap: (RMMapView*) map At: (CGPoint) point;
48 - (void) singleTapOnMap: (RMMapView*) map At: (CGPoint) point; 50 - (void) singleTapOnMap: (RMMapView*) map At: (CGPoint) point;
  51 +- (void) longSingleTapOnMap: (RMMapView*) map At: (CGPoint) point;
49 52
50 - (void) tapOnMarker: (RMMarker*) marker onMap: (RMMapView*) map; 53 - (void) tapOnMarker: (RMMarker*) marker onMap: (RMMapView*) map;
51 - (void) tapOnLabelForMarker: (RMMarker*) marker onMap: (RMMapView*) map; 54 - (void) tapOnLabelForMarker: (RMMarker*) marker onMap: (RMMapView*) map;
@@ -90,6 +90,7 @@ @@ -90,6 +90,7 @@
90 } 90 }
91 91
92 } 92 }
  93 +
93 return self; 94 return self;
94 } 95 }
95 96
@@ -31,7 +31,12 @@ @@ -31,7 +31,12 @@
31 31
32 /// the interface between RMDatabaseCache and FMDB 32 /// the interface between RMDatabaseCache and FMDB
33 @interface RMTileCacheDAO : NSObject { 33 @interface RMTileCacheDAO : NSObject {
34 - FMDatabase* db; 34 + FMDatabase* db;
  35 +
  36 + NSUInteger tileCount;
  37 +
  38 + NSOperationQueue *writeQueue;
  39 + NSRecursiveLock *writeQueueLock;
35 } 40 }
36 41
37 -(id) initWithDatabase: (NSString*)path; 42 -(id) initWithDatabase: (NSString*)path;
@@ -41,7 +46,6 @@ @@ -41,7 +46,6 @@
41 -(void) touchTile: (uint64_t) tileHash withDate: (NSDate*) date; 46 -(void) touchTile: (uint64_t) tileHash withDate: (NSDate*) date;
42 -(void) addData: (NSData*) data LastUsed: (NSDate*)date ForTile: (uint64_t) tileHash; 47 -(void) addData: (NSData*) data LastUsed: (NSDate*)date ForTile: (uint64_t) tileHash;
43 -(void) purgeTiles: (NSUInteger) count; 48 -(void) purgeTiles: (NSUInteger) count;
44 --(void) purgeTilesFromBefore: (NSDate*) date;  
45 -(void) removeAllCachedImages; 49 -(void) removeAllCachedImages;
46 -(void)didReceiveMemoryWarning; 50 -(void)didReceiveMemoryWarning;
47 51
@@ -30,16 +30,18 @@ @@ -30,16 +30,18 @@
30 #import "RMTileCache.h" 30 #import "RMTileCache.h"
31 #import "RMTileImage.h" 31 #import "RMTileImage.h"
32 32
  33 +@interface RMTileCacheDAO ()
  34 +- (NSUInteger)countTiles;
  35 +@end
33 36
34 @implementation RMTileCacheDAO 37 @implementation RMTileCacheDAO
35 38
36 -(void)configureDBForFirstUse 39 -(void)configureDBForFirstUse
37 { 40 {
  41 + [db executeQuery:@"PRAGMA synchronous=OFF"];
  42 + [db executeQuery:@"PRAGMA journal_mode=OFF"];
38 [db executeUpdate:@"CREATE TABLE IF NOT EXISTS ZCACHE (ztileHash INTEGER PRIMARY KEY, zlastUsed DOUBLE, zdata BLOB)"]; 43 [db executeUpdate:@"CREATE TABLE IF NOT EXISTS ZCACHE (ztileHash INTEGER PRIMARY KEY, zlastUsed DOUBLE, zdata BLOB)"];
39 [db executeUpdate:@"CREATE INDEX IF NOT EXISTS zlastUsedIndex ON ZCACHE(zLastUsed)"]; 44 [db executeUpdate:@"CREATE INDEX IF NOT EXISTS zlastUsedIndex ON ZCACHE(zLastUsed)"];
40 - // adding more than once does not seem to break anything  
41 - [db executeUpdate:@"ALTER TABLE ZCACHE ADD COLUMN zInserted DOUBLE"];  
42 - [db executeUpdate:@"CREATE INDEX IF NOT EXISTS zInsertedIndex ON ZCACHE(zInserted)"];  
43 } 45 }
44 46
45 -(id) initWithDatabase: (NSString*)path 47 -(id) initWithDatabase: (NSString*)path
@@ -47,8 +49,12 @@ @@ -47,8 +49,12 @@
47 if (![super init]) 49 if (![super init])
48 return nil; 50 return nil;
49 51
  52 + writeQueue = [NSOperationQueue new];
  53 + [writeQueue setMaxConcurrentOperationCount:1];
  54 + writeQueueLock = [NSRecursiveLock new];
  55 +
50 RMLog(@"Opening database at %@", path); 56 RMLog(@"Opening database at %@", path);
51 - 57 +
52 db = [[FMDatabase alloc] initWithPath:path]; 58 db = [[FMDatabase alloc] initWithPath:path];
53 if (![db open]) 59 if (![db open])
54 { 60 {
@@ -57,129 +63,136 @@ @@ -57,129 +63,136 @@
57 } 63 }
58 64
59 [db setCrashOnErrors:TRUE]; 65 [db setCrashOnErrors:TRUE];
60 - [db setShouldCacheStatements:TRUE]; 66 + [db setShouldCacheStatements:TRUE];
61 67
62 [self configureDBForFirstUse]; 68 [self configureDBForFirstUse];
63 69
  70 + tileCount = [self countTiles];
  71 +
64 return self; 72 return self;
65 } 73 }
66 74
67 - (void)dealloc 75 - (void)dealloc
68 { 76 {
69 - LogMethod();  
70 - [db release];  
71 - [super dealloc]; 77 + LogMethod();
  78 + [writeQueueLock lock];
  79 + [writeQueue release]; writeQueue = nil;
  80 + [writeQueueLock unlock];
  81 + [writeQueueLock release]; writeQueueLock = nil;
  82 + [db close]; [db release]; db = nil;
  83 + [super dealloc];
72 } 84 }
73 85
  86 +- (NSUInteger)count
  87 +{
  88 + return tileCount;
  89 +}
74 90
75 --(NSUInteger) count 91 +- (NSUInteger)countTiles
76 { 92 {
77 - FMResultSet *results = [db executeQuery:@"SELECT COUNT(ztileHash) FROM ZCACHE"]; 93 + [writeQueueLock lock];
78 94
79 NSUInteger count = 0; 95 NSUInteger count = 0;
80 - 96 + FMResultSet *results = [db executeQuery:@"SELECT COUNT(ztileHash) FROM ZCACHE"];
81 if ([results next]) 97 if ([results next])
82 count = [results intForColumnIndex:0]; 98 count = [results intForColumnIndex:0];
83 else 99 else
84 - {  
85 - RMLog(@"Unable to count columns");  
86 - }  
87 - 100 + RMLog(@"Unable to count columns");
88 [results close]; 101 [results close];
  102 +
  103 + [writeQueueLock unlock];
89 104
90 return count; 105 return count;
91 } 106 }
92 107
93 --(NSData*) dataForTile: (uint64_t) tileHash 108 +-(NSData *)dataForTile:(uint64_t)tileHash
94 { 109 {
  110 + [writeQueueLock lock];
  111 +
95 FMResultSet *results = [db executeQuery:@"SELECT zdata FROM ZCACHE WHERE ztilehash = ?", [NSNumber numberWithUnsignedLongLong:tileHash]]; 112 FMResultSet *results = [db executeQuery:@"SELECT zdata FROM ZCACHE WHERE ztilehash = ?", [NSNumber numberWithUnsignedLongLong:tileHash]];
96 113
97 - if ([db hadError])  
98 - { 114 + if ([db hadError]) {
99 RMLog(@"DB error while fetching tile data: %@", [db lastErrorMessage]); 115 RMLog(@"DB error while fetching tile data: %@", [db lastErrorMessage]);
100 return nil; 116 return nil;
101 } 117 }
102 -  
103 - NSData *data = nil;  
104 - 118 +
  119 + NSData *data = nil;
105 if ([results next]) 120 if ([results next])
106 - {  
107 data = [results dataForColumnIndex:0]; 121 data = [results dataForColumnIndex:0];
108 - }  
109 122
110 [results close]; 123 [results close];
111 124
  125 + [writeQueueLock unlock];
  126 +
112 return data; 127 return data;
113 } 128 }
114 129
115 -(void) purgeTiles: (NSUInteger) count; 130 -(void) purgeTiles: (NSUInteger) count;
116 { 131 {
117 - RMLog(@"purging %u old tiles from db cache", count);  
118 -  
119 - // does not work: "DELETE FROM ZCACHE ORDER BY zlastUsed LIMIT"  
120 -  
121 - BOOL result = [db executeUpdate: @"DELETE FROM ZCACHE WHERE ztileHash IN (SELECT ztileHash FROM ZCACHE ORDER BY zlastUsed LIMIT ? )",  
122 - [NSNumber numberWithUnsignedInt: count]];  
123 - if (result == NO) {  
124 - RMLog(@"Error purging cache");  
125 - } 132 + RMLog(@"purging %u old tiles from db cache", count);
126 133
127 -} 134 + [writeQueueLock lock];
  135 + BOOL result = [db executeUpdate: @"DELETE FROM ZCACHE WHERE ztileHash IN (SELECT ztileHash FROM ZCACHE ORDER BY zlastUsed LIMIT ? )", [NSNumber numberWithUnsignedInt: count]];
  136 + [db executeQuery:@"VACUUM"];
  137 + tileCount = [self countTiles];
  138 + [writeQueueLock unlock];
128 139
129 --(void) purgeTilesFromBefore: (NSDate*) date;  
130 -{  
131 - NSUInteger count = 0;  
132 - FMResultSet *results = [db executeQuery:@"SELECT COUNT(ztileHash) FROM ZCACHE WHERE zInserted < ?", date];  
133 - if ([results next]) {  
134 - count = [results intForColumnIndex:0];  
135 - RMLog(@"Will purge %i tile(s) from before %@", count, date);  
136 - }  
137 - [results close];  
138 -  
139 - if (count == 0) {  
140 - return;  
141 - }  
142 -  
143 - BOOL result = [db executeUpdate: @"DELETE FROM ZCACHE WHERE zInserted < ?",  
144 - date];  
145 - if (result == NO) {  
146 - RMLog(@"Error purging cache");  
147 - } 140 + if (result == NO) {
  141 + RMLog(@"Error purging cache");
  142 + }
148 } 143 }
149 144
150 -(void) removeAllCachedImages 145 -(void) removeAllCachedImages
151 { 146 {
152 - BOOL result = [db executeUpdate: @"DELETE FROM ZCACHE"];  
153 - if (result == NO) {  
154 - RMLog(@"Error purging all cache");  
155 - } 147 + [writeQueue addOperationWithBlock:^{
  148 + [writeQueueLock lock];
  149 + BOOL result = [db executeUpdate: @"DELETE FROM ZCACHE"];
  150 + [db executeQuery:@"VACUUM"];
  151 + [writeQueueLock unlock];
  152 +
  153 + if (result == NO) {
  154 + RMLog(@"Error purging all cache");
  155 + }
  156 +
  157 + tileCount = [self countTiles];
  158 + }];
156 } 159 }
157 160
158 -(void) touchTile: (uint64_t) tileHash withDate: (NSDate*) date 161 -(void) touchTile: (uint64_t) tileHash withDate: (NSDate*) date
159 { 162 {
160 - BOOL result = [db executeUpdate: @"UPDATE ZCACHE SET zlastUsed = ? WHERE ztileHash = ? ",  
161 - date, [NSNumber numberWithUnsignedInt: tileHash]];  
162 -  
163 - if (result == NO) {  
164 - RMLog(@"Error touching tile");  
165 - } 163 + [writeQueue addOperationWithBlock:^{
  164 + [writeQueueLock lock];
  165 + BOOL result = [db executeUpdate: @"UPDATE ZCACHE SET zlastUsed = ? WHERE ztileHash = ? ", date, [NSNumber numberWithUnsignedInt: tileHash]];
  166 + [writeQueueLock unlock];
  167 +
  168 + if (result == NO) {
  169 + RMLog(@"Error touching tile");
  170 + }
  171 + }];
166 } 172 }
167 173
168 -(void) addData: (NSData*) data LastUsed: (NSDate*)date ForTile: (uint64_t) tileHash 174 -(void) addData: (NSData*) data LastUsed: (NSDate*)date ForTile: (uint64_t) tileHash
169 { 175 {
170 - // Fixme  
171 -// RMLog(@"addData\t%d", tileHash);  
172 - BOOL result = [db executeUpdate:@"INSERT OR REPLACE INTO ZCACHE (ztileHash, zlastUsed, zInserted, zdata) VALUES (?, ?, ?, ?)",  
173 - [NSNumber numberWithUnsignedLongLong:tileHash], date, [NSDate date], data];  
174 - if (result == NO)  
175 - {  
176 - RMLog(@"Error occured adding data");  
177 - } 176 + [writeQueue addOperationWithBlock:^{
  177 +// RMLog(@"addData\t%d", tileHash);
  178 +
  179 + [writeQueueLock lock];
  180 + BOOL result = [db executeUpdate:@"INSERT OR IGNORE INTO ZCACHE (ztileHash, zlastUsed, zdata) VALUES (?, ?, ?)",
  181 + [NSNumber numberWithUnsignedLongLong:tileHash], date, data];
  182 + [writeQueueLock unlock];
  183 +
  184 + if (result == NO)
  185 + {
  186 + RMLog(@"Error occured adding data");
  187 + } else
  188 + tileCount++;
  189 + }];
178 } 190 }
179 191
180 -(void)didReceiveMemoryWarning 192 -(void)didReceiveMemoryWarning
181 { 193 {
182 - [db clearCachedStatements]; 194 + RMLog(@"Low memory in the tilecache");
  195 + [writeQueue cancelAllOperations];
183 } 196 }
184 197
185 @end 198 @end
@@ -40,7 +40,6 @@ typedef NSImage UIImage; @@ -40,7 +40,6 @@ typedef NSImage UIImage;
40 #import "FMDatabase.h" 40 #import "FMDatabase.h"
41 41
42 @class RMTileImage; 42 @class RMTileImage;
43 -@class NSData;  
44 43
45 @interface RMTileImage : NSObject { 44 @interface RMTileImage : NSObject {
46 // I know this is a bit nasty. 45 // I know this is a bit nasty.
@@ -180,15 +180,14 @@ @@ -180,15 +180,14 @@
180 180
181 [customActions setObject:[NSNull null] forKey:@"position"]; 181 [customActions setObject:[NSNull null] forKey:@"position"];
182 [customActions setObject:[NSNull null] forKey:@"bounds"]; 182 [customActions setObject:[NSNull null] forKey:@"bounds"];
183 - [customActions setObject:[NSNull null] forKey:kCAOnOrderOut];  
184 - [customActions setObject:[NSNull null] forKey:kCAOnOrderIn];  
185 -  
186 - CATransition *fadein = [[CATransition alloc] init];  
187 - fadein.duration = 0.3;  
188 - fadein.type = kCATransitionReveal;  
189 - [customActions setObject:fadein forKey:@"contents"];  
190 - [fadein release];  
191 - 183 + [customActions setObject:[NSNull null] forKey:kCAOnOrderOut];
  184 + [customActions setObject:[NSNull null] forKey:kCAOnOrderIn];
  185 +
  186 + CATransition *reveal = [[CATransition alloc] init];
  187 + reveal.duration = 0.3;
  188 + reveal.type = kCATransitionFade;
  189 + [customActions setObject:reveal forKey:@"contents"];
  190 + [reveal release];
192 191
193 layer.actions=customActions; 192 layer.actions=customActions;
194 193
@@ -147,11 +147,11 @@ @@ -147,11 +147,11 @@
147 { 147 {
148 // RMLog(@"addTile: %d %d", tile.x, tile.y); 148 // RMLog(@"addTile: %d %d", tile.x, tile.y);
149 149
150 - RMTileImage *dummyTile = [RMTileImage dummyTile:tile];  
151 - RMTileImage *tileImage = [images member:dummyTile];  
152 - 150 + RMTileImage *dummyTile = [RMTileImage dummyTile:tile];
  151 + RMTileImage *tileImage = [images member:dummyTile];
  152 +
153 if (tileImage != nil) 153 if (tileImage != nil)
154 - { 154 + {
155 [tileImage setScreenLocation:screenLocation]; 155 [tileImage setScreenLocation:screenLocation];
156 [images addObject:dummyTile]; 156 [images addObject:dummyTile];
157 } 157 }
@@ -214,7 +214,10 @@ @@ -214,7 +214,10 @@
214 [self addTile:normalisedTile At:screenLocation]; 214 [self addTile:normalisedTile At:screenLocation];
215 } 215 }
216 } 216 }
217 - 217 +
  218 + // Performance issue!
  219 + break;
  220 +
218 // adjust rect for next zoom level down until we're at minimum 221 // adjust rect for next zoom level down until we're at minimum
219 if (--rect.origin.tile.zoom <= minimumZoom) 222 if (--rect.origin.tile.zoom <= minimumZoom)
220 break; 223 break;
@@ -65,7 +65,6 @@ @@ -65,7 +65,6 @@
65 65
66 -(void) dealloc 66 -(void) dealloc
67 { 67 {
68 - [[NSNotificationCenter defaultCenter] removeObserver:self];  
69 [super dealloc]; 68 [super dealloc];
70 } 69 }
71 70
@@ -105,19 +104,7 @@ @@ -105,19 +104,7 @@
105 if ([content mercatorToTileProjection] == nil || [content 104 if ([content mercatorToTileProjection] == nil || [content
106 mercatorToScreenProjection] == nil) 105 mercatorToScreenProjection] == nil)
107 return; 106 return;
108 -  
109 - // delay display of new images until expensive operations are  
110 - //allowed  
111 - [[NSNotificationCenter defaultCenter] removeObserver:self  
112 - name:RMResumeExpensiveOperations object:nil];  
113 - if ([RMMapContents performExpensiveOperations] == NO)  
114 - {  
115 - [[NSNotificationCenter defaultCenter] addObserver:self  
116 - selector:@selector(updateLoadedImages)  
117 - name:RMResumeExpensiveOperations object:nil];  
118 - return;  
119 - }  
120 - 107 +
121 if ([self screenIsLoaded]) 108 if ([self screenIsLoaded])
122 return; 109 return;
123 110