/MapView/Map/RMTileImageSet.m

http://github.com/route-me/route-me · Objective C · 548 lines · 404 code · 81 blank · 63 comment · 70 complexity · 14a80c12aa30c431e883cd134e41eb1b MD5 · raw file

  1. //
  2. // RMTileImageSet.m
  3. //
  4. // Copyright (c) 2008-2009, Route-Me Contributors
  5. // All rights reserved.
  6. //
  7. // Redistribution and use in source and binary forms, with or without
  8. // modification, are permitted provided that the following conditions are met:
  9. //
  10. // * Redistributions of source code must retain the above copyright notice, this
  11. // list of conditions and the following disclaimer.
  12. // * Redistributions in binary form must reproduce the above copyright notice,
  13. // this list of conditions and the following disclaimer in the documentation
  14. // and/or other materials provided with the distribution.
  15. //
  16. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  17. // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  18. // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  19. // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  20. // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  21. // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  22. // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  23. // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  24. // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  25. // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  26. // POSSIBILITY OF SUCH DAMAGE.
  27. #import "RMTileImageSet.h"
  28. #import "RMTileImage.h"
  29. #import "RMPixel.h"
  30. #import "RMTileSource.h"
  31. // For notification strings
  32. #import "RMTileLoader.h"
  33. #import "RMMercatorToTileProjection.h"
  34. @implementation RMTileImageSet
  35. @synthesize delegate, tileDepth;
  36. -(id) initWithDelegate: (id) _delegate
  37. {
  38. if (![super init])
  39. return nil;
  40. tileSource = nil;
  41. self.delegate = _delegate;
  42. images = [[NSMutableSet alloc] init];
  43. [[NSNotificationCenter defaultCenter]
  44. addObserver:self
  45. selector:@selector(tileImageLoaded:)
  46. name:RMMapImageLoadedNotification
  47. object:nil
  48. ];
  49. return self;
  50. }
  51. -(void) dealloc
  52. {
  53. [[NSNotificationCenter defaultCenter] removeObserver:self];
  54. [self removeAllTiles];
  55. [images release];
  56. [super dealloc];
  57. }
  58. -(void) removeTile: (RMTile) tile
  59. {
  60. RMTileImage *img;
  61. NSAssert(!RMTileIsDummy(tile), @"attempted to remove dummy tile");
  62. if (RMTileIsDummy(tile))
  63. {
  64. RMLog(@"attempted to remove dummy tile...??");
  65. return;
  66. }
  67. RMTileImage *dummyTile = [RMTileImage dummyTile:tile];
  68. img = [images member:dummyTile];
  69. if (!img) {
  70. return;
  71. }
  72. if ([delegate respondsToSelector: @selector(tileRemoved:)])
  73. {
  74. [delegate tileRemoved:tile];
  75. }
  76. [[NSNotificationCenter defaultCenter] postNotificationName:RMMapImageRemovedFromScreenNotification object:img];
  77. [images removeObject:dummyTile];
  78. }
  79. -(void) removeAllTiles
  80. {
  81. @synchronized(self){
  82. for (RMTileImage * img in [images allObjects]) {
  83. [self removeTile: img.tile];
  84. }
  85. }
  86. }
  87. - (void) setTileSource: (id<RMTileSource>)newTileSource
  88. {
  89. [self removeAllTiles];
  90. tileSource = newTileSource;
  91. }
  92. -(void) addTile: (RMTile) tile WithImage: (RMTileImage *)image At: (CGRect) screenLocation
  93. {
  94. BOOL tileNeeded;
  95. tileNeeded = YES;
  96. @synchronized(self){
  97. for (RMTileImage *img in images)
  98. {
  99. if (![img isLoaded])
  100. {
  101. continue;
  102. }
  103. if ([self isTile:tile worseThanTile:img.tile])
  104. {
  105. tileNeeded = NO;
  106. break;
  107. }
  108. }
  109. }
  110. if (!tileNeeded) {
  111. return;
  112. }
  113. if ([image isLoaded]) {
  114. [self removeTilesWorseThan:image];
  115. }
  116. image.screenLocation = screenLocation;
  117. [images addObject:image];
  118. if (!RMTileIsDummy(image.tile))
  119. {
  120. if([delegate respondsToSelector:@selector(tileAdded:WithImage:)])
  121. {
  122. [delegate tileAdded:tile WithImage:image];
  123. }
  124. [[NSNotificationCenter defaultCenter] postNotificationName:RMMapImageAddedToScreenNotification object:image];
  125. }
  126. }
  127. -(void) addTile: (RMTile) tile At: (CGRect) screenLocation
  128. {
  129. // RMLog(@"addTile: %d %d", tile.x, tile.y);
  130. RMTileImage *dummyTile = [RMTileImage dummyTile:tile];
  131. RMTileImage *tileImage = [images member:dummyTile];
  132. if (tileImage != nil)
  133. {
  134. [tileImage setScreenLocation:screenLocation];
  135. [images addObject:dummyTile];
  136. }
  137. else
  138. {
  139. RMTileImage *image = [tileSource tileImage:tile];
  140. if (image != nil)
  141. [self addTile:tile WithImage:image At:screenLocation];
  142. }
  143. }
  144. // Add tiles inside rect protected to bounds. Return rectangle containing bounds
  145. // extended to full tile loading area
  146. -(CGRect) addTiles: (RMTileRect)rect ToDisplayIn:(CGRect)bounds
  147. {
  148. // RMLog(@"addTiles: %d %d - %f %f", rect.origin.tile.x, rect.origin.tile.y, rect.size.width, rect.size.height);
  149. RMTile t;
  150. float pixelsPerTile = bounds.size.width / rect.size.width;
  151. RMTileRect roundedRect = RMTileRectRound(rect);
  152. // The number of tiles we'll load in the vertical and horizontal directions
  153. int tileRegionWidth = (int)roundedRect.size.width;
  154. int tileRegionHeight = (int)roundedRect.size.height;
  155. id<RMMercatorToTileProjection> proj = [tileSource mercatorToTileProjection];
  156. short minimumZoom = [tileSource minZoom], alternateMinimum;
  157. // Now we translate the loaded region back into screen space for loadedBounds.
  158. CGRect newLoadedBounds;
  159. newLoadedBounds.origin.x = bounds.origin.x - (rect.origin.offset.x * pixelsPerTile);
  160. newLoadedBounds.origin.y = bounds.origin.y - (rect.origin.offset.y * pixelsPerTile);
  161. newLoadedBounds.size.width = tileRegionWidth * pixelsPerTile;
  162. newLoadedBounds.size.height = tileRegionHeight * pixelsPerTile;
  163. alternateMinimum = zoom - tileDepth - 1;
  164. if (minimumZoom < alternateMinimum)
  165. {
  166. minimumZoom = alternateMinimum;
  167. }
  168. for (;;)
  169. {
  170. CGRect screenLocation;
  171. screenLocation.size.width = pixelsPerTile;
  172. screenLocation.size.height = pixelsPerTile;
  173. t.zoom = rect.origin.tile.zoom;
  174. for (t.x = roundedRect.origin.tile.x; t.x < roundedRect.origin.tile.x + tileRegionWidth; t.x++)
  175. {
  176. for (t.y = roundedRect.origin.tile.y; t.y < roundedRect.origin.tile.y + tileRegionHeight; t.y++)
  177. {
  178. RMTile normalisedTile = [proj normaliseTile: t];
  179. if (RMTileIsDummy(normalisedTile))
  180. continue;
  181. // this regrouping of terms is better for calculation precision (issue 128)
  182. screenLocation.origin.x = bounds.origin.x + (t.x - rect.origin.tile.x - rect.origin.offset.x) * pixelsPerTile;
  183. screenLocation.origin.y = bounds.origin.y + (t.y - rect.origin.tile.y - rect.origin.offset.y) * pixelsPerTile;
  184. [self addTile:normalisedTile At:screenLocation];
  185. }
  186. }
  187. // adjust rect for next zoom level down until we're at minimum
  188. if (--rect.origin.tile.zoom <= minimumZoom)
  189. break;
  190. if (rect.origin.tile.x & 1)
  191. rect.origin.offset.x += 1.0;
  192. if (rect.origin.tile.y & 1)
  193. rect.origin.offset.y += 1.0;
  194. rect.origin.tile.x /= 2;
  195. rect.origin.tile.y /= 2;
  196. rect.size.width *= 0.5;
  197. rect.size.height *= 0.5;
  198. rect.origin.offset.x *= 0.5;
  199. rect.origin.offset.y *= 0.5;
  200. pixelsPerTile = bounds.size.width / rect.size.width;
  201. roundedRect = RMTileRectRound(rect);
  202. // The number of tiles we'll load in the vertical and horizontal directions
  203. tileRegionWidth = (int)roundedRect.size.width;
  204. tileRegionHeight = (int)roundedRect.size.height;
  205. }
  206. return newLoadedBounds;
  207. }
  208. -(RMTileImage*) imageWithTile: (RMTile) tile
  209. {
  210. RMTileImage *dummyTile = [RMTileImage dummyTile:tile];
  211. return [images member:dummyTile];
  212. }
  213. -(NSUInteger) count
  214. {
  215. return [images count];
  216. }
  217. - (void)moveBy: (CGSize) delta
  218. {
  219. @synchronized(self){
  220. for (RMTileImage *image in images)
  221. {
  222. [image moveBy: delta];
  223. }
  224. }
  225. }
  226. - (void)zoomByFactor: (float) zoomFactor near:(CGPoint) center
  227. {
  228. @synchronized(self){
  229. for (RMTileImage *image in images)
  230. {
  231. [image zoomByFactor:zoomFactor near:center];
  232. }
  233. }
  234. }
  235. /*
  236. - (void) drawRect:(CGRect) rect
  237. {
  238. for (RMTileImage *image in images)
  239. {
  240. [image draw];
  241. }
  242. }
  243. */
  244. - (void) printDebuggingInformation
  245. {
  246. float biggestSeamRight = 0.0f;
  247. float biggestSeamDown = 0.0f;
  248. for (RMTileImage *image in images)
  249. {
  250. CGRect location = [image screenLocation];
  251. /* RMLog(@"Image at %f, %f %f %f",
  252. location.origin.x,
  253. location.origin.y,
  254. location.origin.x + location.size.width,
  255. location.origin.y + location.size.height);
  256. */
  257. float seamRight = INFINITY;
  258. float seamDown = INFINITY;
  259. @synchronized(self){
  260. for (RMTileImage *other_image in images)
  261. {
  262. CGRect other_location = [other_image screenLocation];
  263. if (other_location.origin.x > location.origin.x)
  264. seamRight = MIN(seamRight, other_location.origin.x - (location.origin.x + location.size.width));
  265. if (other_location.origin.y > location.origin.y)
  266. seamDown = MIN(seamDown, other_location.origin.y - (location.origin.y + location.size.height));
  267. }
  268. }
  269. if (seamRight != INFINITY)
  270. biggestSeamRight = MAX(biggestSeamRight, seamRight);
  271. if (seamDown != INFINITY)
  272. biggestSeamDown = MAX(biggestSeamDown, seamDown);
  273. }
  274. RMLog(@"Biggest seam right: %f down: %f", biggestSeamRight, biggestSeamDown);
  275. }
  276. - (void)cancelLoading
  277. {
  278. @synchronized(self){
  279. for (RMTileImage *image in images)
  280. {
  281. [image cancelLoading];
  282. }
  283. }
  284. }
  285. - (RMTileImage *)anyTileImage {
  286. return [images anyObject];
  287. }
  288. - (short)zoom
  289. {
  290. return zoom;
  291. }
  292. - (void)setZoom:(short)value
  293. {
  294. if (zoom == value) {
  295. // no need to act
  296. return;
  297. }
  298. zoom = value;
  299. @synchronized(self){
  300. for (RMTileImage *image in [images allObjects])
  301. {
  302. if (![image isLoaded]) {
  303. continue;
  304. }
  305. [self removeTilesWorseThan:image];
  306. }
  307. }
  308. }
  309. - (BOOL)fullyLoaded
  310. {
  311. BOOL fullyLoaded = YES;
  312. @synchronized(self){
  313. for (RMTileImage *image in images)
  314. {
  315. if (![image isLoaded])
  316. {
  317. fullyLoaded = NO;
  318. break;
  319. }
  320. }
  321. }
  322. return fullyLoaded;
  323. }
  324. - (void)tileImageLoaded:(NSNotification *)notification
  325. {
  326. RMTileImage *img = (RMTileImage *)[notification object];
  327. if (!img || img != [images member:img])
  328. {
  329. // i don't contain img, it may be already removed or in another set
  330. return;
  331. }
  332. [self removeTilesWorseThan:img];
  333. }
  334. - (void)removeTilesWorseThan:(RMTileImage *)newImage {
  335. RMTile newTile = newImage.tile;
  336. if (newTile.zoom > zoom) {
  337. // no tiles are worse since this one is too detailed to keep long-term
  338. return;
  339. }
  340. @synchronized(self){
  341. for (RMTileImage *oldImage in [images allObjects])
  342. {
  343. RMTile oldTile = oldImage.tile;
  344. if (oldImage == newImage)
  345. {
  346. continue;
  347. }
  348. if ([self isTile:oldTile worseThanTile:newTile])
  349. {
  350. [oldImage cancelLoading];
  351. [self removeTile:oldTile];
  352. }
  353. }
  354. }
  355. }
  356. - (BOOL)isTile:(RMTile)subject worseThanTile:(RMTile)object
  357. {
  358. short subjZ, objZ;
  359. uint32_t sx, sy, ox, oy;
  360. objZ = object.zoom;
  361. if (objZ > zoom)
  362. {
  363. // can't be worse than this tile, it's too detailed to keep long-term
  364. return NO;
  365. }
  366. subjZ = subject.zoom;
  367. if (subjZ + tileDepth >= zoom && subjZ <= zoom)
  368. {
  369. // this tile isn't bad, it's within zoom limits
  370. return NO;
  371. }
  372. sx = subject.x;
  373. sy = subject.y;
  374. ox = object.x;
  375. oy = object.y;
  376. if (subjZ < objZ)
  377. {
  378. // old tile is larger & blurrier
  379. unsigned int dz = objZ - subjZ;
  380. ox >>= dz;
  381. oy >>= dz;
  382. }
  383. else if (objZ < subjZ)
  384. {
  385. // old tile is smaller & more detailed
  386. unsigned int dz = subjZ - objZ;
  387. sx >>= dz;
  388. sy >>= dz;
  389. }
  390. if (sx != ox || sy != oy)
  391. {
  392. // Tiles don't overlap
  393. return NO;
  394. }
  395. if (abs(zoom - subjZ) < abs(zoom - objZ))
  396. {
  397. // subject is closer to desired zoom level than object, so it's not worse
  398. return NO;
  399. }
  400. return YES;
  401. }
  402. -(void) removeTilesOutsideOf: (RMTileRect)rect
  403. {
  404. uint32_t minX, maxX, minY, maxY, span;
  405. short currentZoom = rect.origin.tile.zoom;
  406. RMTile wrappedTile;
  407. id<RMMercatorToTileProjection> proj = [tileSource mercatorToTileProjection];
  408. rect = RMTileRectRound(rect);
  409. minX = rect.origin.tile.x;
  410. span = rect.size.width > 1.0f ? (uint32_t)rect.size.width - 1 : 0;
  411. maxX = rect.origin.tile.x + span;
  412. minY = rect.origin.tile.y;
  413. span = rect.size.height > 1.0f ? (uint32_t)rect.size.height - 1 : 0;
  414. maxY = rect.origin.tile.y + span;
  415. wrappedTile.x = maxX;
  416. wrappedTile.y = maxY;
  417. wrappedTile.zoom = rect.origin.tile.zoom;
  418. wrappedTile = [proj normaliseTile:wrappedTile];
  419. if (!RMTileIsDummy(wrappedTile))
  420. {
  421. maxX = wrappedTile.x;
  422. }
  423. for(RMTileImage *img in [images allObjects])
  424. {
  425. RMTile tile = img.tile;
  426. short tileZoom = tile.zoom;
  427. uint32_t x, y, zoomedMinX, zoomedMaxX, zoomedMinY, zoomedMaxY;
  428. x = tile.x;
  429. y = tile.y;
  430. zoomedMinX = minX;
  431. zoomedMaxX = maxX;
  432. zoomedMinY = minY;
  433. zoomedMaxY = maxY;
  434. if (tileZoom < currentZoom)
  435. {
  436. // Tile is too large for current zoom level
  437. unsigned int dz = currentZoom - tileZoom;
  438. zoomedMinX >>= dz;
  439. zoomedMaxX >>= dz;
  440. zoomedMinY >>= dz;
  441. zoomedMaxY >>= dz;
  442. }
  443. else
  444. {
  445. // Tile is too small & detailed for current zoom level
  446. unsigned int dz = tileZoom - currentZoom;
  447. x >>= dz;
  448. y >>= dz;
  449. }
  450. if (y >= zoomedMinY && y <= zoomedMaxY)
  451. {
  452. if (zoomedMinX <= zoomedMaxX)
  453. {
  454. if (x >= zoomedMinX && x <= zoomedMaxX)
  455. continue;
  456. }
  457. else
  458. {
  459. if (x >= zoomedMinX || x <= zoomedMaxX)
  460. continue;
  461. }
  462. }
  463. // if haven't continued, tile is outside of rect
  464. [self removeTile:tile];
  465. }
  466. }
  467. @end