Showing
20 changed files
with
403 additions
and
236 deletions
@@ -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; |
@@ -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 |
-
Please register or login to post a comment