/MapView/Map/RMFractalTileProjection.m

http://github.com/route-me/route-me · Objective C · 201 lines · 128 code · 40 blank · 33 comment · 11 complexity · b1f571d9dfe056107c44297ebd5adb0c MD5 · raw file

  1. //
  2. // RMFractalTileProjection.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 "RMFractalTileProjection.h"
  28. #import "RMMercatorToScreenProjection.h"
  29. #import "RMProjection.h"
  30. #import <math.h>
  31. @implementation RMFractalTileProjection
  32. @synthesize maxZoom, minZoom;
  33. @synthesize tileSideLength;
  34. @synthesize planetBounds;
  35. -(id) initFromProjection:(RMProjection*)projection tileSideLength:(NSUInteger)aTileSideLength maxZoom: (NSUInteger) aMaxZoom minZoom: (NSUInteger) aMinZoom
  36. {
  37. if (![super init])
  38. return nil;
  39. // We don't care about the rest of the projection... just the bounds is important.
  40. planetBounds = [projection planetBounds];
  41. if (planetBounds.size.width == 0.0f || planetBounds.size.height == 0.0f)
  42. {
  43. /// \bug magic string literals
  44. @throw [NSException exceptionWithName:@"RMUnknownBoundsException"
  45. reason:@"RMFractalTileProjection was initialised with a projection with unknown bounds"
  46. userInfo:nil];
  47. }
  48. tileSideLength = aTileSideLength;
  49. maxZoom = aMaxZoom;
  50. minZoom = aMinZoom;
  51. scaleFactor = log2(planetBounds.size.width / tileSideLength);
  52. return self;
  53. }
  54. - (void) setTileSideLength: (NSUInteger) aTileSideLength
  55. {
  56. tileSideLength = aTileSideLength;
  57. scaleFactor = log2(planetBounds.size.width / tileSideLength);
  58. }
  59. - (void) setMinZoom: (NSUInteger) aMinZoom
  60. {
  61. minZoom = aMinZoom;
  62. }
  63. - (void) setMaxZoom: (NSUInteger) aMaxZoom
  64. {
  65. maxZoom = aMaxZoom;
  66. }
  67. - (float) normaliseZoom: (float) zoom
  68. {
  69. float normalised_zoom = roundf(zoom);
  70. if (normalised_zoom > maxZoom)
  71. normalised_zoom = maxZoom;
  72. if (normalised_zoom < minZoom)
  73. normalised_zoom = minZoom;
  74. return normalised_zoom;
  75. }
  76. - (float) limitFromNormalisedZoom: (float) zoom
  77. {
  78. return exp2f(zoom);
  79. }
  80. - (RMTile) normaliseTile: (RMTile) tile
  81. {
  82. // The mask contains a 1 for every valid x-coordinate bit.
  83. uint32_t mask = 1;
  84. for (int i = 0; i < tile.zoom; i++)
  85. mask <<= 1;
  86. mask -= 1;
  87. tile.x &= mask;
  88. // If the tile's y coordinate is off the screen
  89. if (tile.y & (~mask))
  90. {
  91. return RMTileDummy();
  92. }
  93. return tile;
  94. }
  95. - (RMProjectedPoint) constrainPointHorizontally: (RMProjectedPoint) aPoint
  96. {
  97. while (aPoint.easting < planetBounds.origin.easting)
  98. aPoint.easting += planetBounds.size.width;
  99. while (aPoint.easting > (planetBounds.origin.easting + planetBounds.size.width))
  100. aPoint.easting -= planetBounds.size.width;
  101. return aPoint;
  102. }
  103. - (RMTilePoint) projectInternal: (RMProjectedPoint)aPoint normalisedZoom:(float)zoom limit:(float) limit
  104. {
  105. RMTilePoint tile;
  106. RMProjectedPoint newPoint = [self constrainPointHorizontally:aPoint];
  107. double x = (newPoint.easting - planetBounds.origin.easting) / planetBounds.size.width * limit;
  108. // Unfortunately, y is indexed from the bottom left.. hence we have to translate it.
  109. double y = (double)limit * ((planetBounds.origin.northing - newPoint.northing) / planetBounds.size.height + 1);
  110. tile.tile.x = (uint32_t)x;
  111. tile.tile.y = (uint32_t)y;
  112. tile.tile.zoom = zoom;
  113. tile.offset.x = (float)x - tile.tile.x;
  114. tile.offset.y = (float)y - tile.tile.y;
  115. return tile;
  116. }
  117. - (RMTilePoint) project: (RMProjectedPoint)aPoint atZoom:(float)zoom
  118. {
  119. float normalised_zoom = [self normaliseZoom:zoom];
  120. float limit = [self limitFromNormalisedZoom:normalised_zoom];
  121. return [self projectInternal:aPoint normalisedZoom:normalised_zoom limit:limit];
  122. }
  123. - (RMTileRect) projectRect: (RMProjectedRect)aRect atZoom:(float)zoom
  124. {
  125. /// \bug assignment of float to int, WTF?
  126. int normalised_zoom = [self normaliseZoom:zoom];
  127. float limit = [self limitFromNormalisedZoom:normalised_zoom];
  128. RMTileRect tileRect;
  129. // The origin for projectInternal will have to be the top left instead of the bottom left.
  130. RMProjectedPoint topLeft = aRect.origin;
  131. topLeft.northing += aRect.size.height;
  132. tileRect.origin = [self projectInternal:topLeft normalisedZoom:normalised_zoom limit:limit];
  133. tileRect.size.width = aRect.size.width / planetBounds.size.width * limit;
  134. tileRect.size.height = aRect.size.height / planetBounds.size.height * limit;
  135. return tileRect;
  136. }
  137. -(RMTilePoint) project: (RMProjectedPoint)aPoint atScale:(float)scale
  138. {
  139. return [self project:aPoint atZoom:[self calculateZoomFromScale:scale]];
  140. }
  141. -(RMTileRect) projectRect: (RMProjectedRect)aRect atScale:(float)scale
  142. {
  143. return [self projectRect:aRect atZoom:[self calculateZoomFromScale:scale]];
  144. }
  145. -(RMTileRect) project: (RMMercatorToScreenProjection*)screen;
  146. {
  147. return [self projectRect:[screen projectedBounds] atScale:[screen metersPerPixel]];
  148. }
  149. -(float) calculateZoomFromScale: (float) scale
  150. { // zoom = log2(bounds.width/tileSideLength) - log2(s)
  151. return scaleFactor - log2(scale);
  152. }
  153. -(float) calculateNormalisedZoomFromScale: (float) scale
  154. {
  155. return [self normaliseZoom:[self calculateZoomFromScale:scale]];
  156. }
  157. -(float) calculateScaleFromZoom: (float) zoom
  158. {
  159. return planetBounds.size.width / tileSideLength / exp2(zoom);
  160. }
  161. @end