PageRenderTime 53ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/MapView/Map/RMMercatorToScreenProjection.m

http://github.com/route-me/route-me
Objective C | 336 lines | 191 code | 54 blank | 91 comment | 5 complexity | a2e775c77646aaf7ee7414196c05e640 MD5 | raw file
  1. //
  2. // RMMercatorToScreenProjection.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 "RMGlobalConstants.h"
  28. #import "RMMercatorToScreenProjection.h"
  29. #include "RMProjection.h"
  30. @implementation RMMercatorToScreenProjection
  31. @synthesize projection;
  32. @synthesize origin;
  33. -(void)deepCopy:(RMMercatorToScreenProjection *)copy{
  34. screenBounds=copy.screenBounds;
  35. projection=copy.projection;
  36. metersPerPixel=copy.metersPerPixel;
  37. origin=copy.origin;
  38. }
  39. - (id) initFromProjection: (RMProjection*) aProjection ToScreenBounds: (CGRect)aScreenBounds;
  40. {
  41. if (![super init])
  42. return nil;
  43. screenBounds = aScreenBounds;
  44. projection = [aProjection retain];
  45. metersPerPixel = 1;
  46. return self;
  47. }
  48. - (void) dealloc
  49. {
  50. [projection release];
  51. [super dealloc];
  52. }
  53. // Deltas in screen coordinates.
  54. - (RMProjectedPoint)movePoint: (RMProjectedPoint)aPoint by:(CGSize) delta
  55. {
  56. RMProjectedSize XYDelta = [self projectScreenSizeToXY:delta];
  57. aPoint.easting += XYDelta.width;
  58. aPoint.northing += XYDelta.height;
  59. aPoint = [projection wrapPointHorizontally:aPoint];
  60. return aPoint;
  61. }
  62. - (RMProjectedRect)moveRect: (RMProjectedRect)aRect by:(CGSize) delta
  63. {
  64. aRect.origin = [self movePoint:aRect.origin by:delta];
  65. return aRect;
  66. }
  67. - (RMProjectedPoint)zoomPoint: (RMProjectedPoint)aPoint byFactor: (float)factor near:(CGPoint) aPixelPoint
  68. {
  69. RMProjectedPoint XYPivot = [self projectScreenPointToXY:aPixelPoint];
  70. RMProjectedPoint result = RMScaleProjectedPointAboutPoint(aPoint, factor, XYPivot);
  71. result = [projection wrapPointHorizontally:result];
  72. // RMLog(@"RMScaleMercatorPointAboutPoint %f %f about %f %f to %f %f", point.x, point.y, mercatorPivot.x, mercatorPivot.y, result.x, result.y);
  73. return result;
  74. }
  75. - (RMProjectedRect)zoomRect: (RMProjectedRect)aRect byFactor: (float)factor near:(CGPoint) aPixelPoint
  76. {
  77. RMProjectedPoint XYPivot = [self projectScreenPointToXY:aPixelPoint];
  78. RMProjectedRect result = RMScaleProjectedRectAboutPoint(aRect, factor, XYPivot);
  79. result.origin = [projection wrapPointHorizontally:result.origin];
  80. return result;
  81. }
  82. -(void) moveScreenBy: (CGSize)delta
  83. {
  84. // RMLog(@"move screen from %f %f", origin.easting, origin.y);
  85. // origin.easting -= delta.width * scale;
  86. // origin.y += delta.height * scale;
  87. // Reverse the delta - if the screen's contents moves left, the origin moves right.
  88. // It makes sense if you think about it long enough and squint your eyes a bit.
  89. delta.width = -delta.width;
  90. delta.height = -delta.height;
  91. origin = [self movePoint:origin by:delta];
  92. // RMLog(@"to %f %f", origin.easting, origin.y);
  93. }
  94. - (void) zoomScreenByFactor: (float) factor near:(CGPoint) aPixelPoint;
  95. {
  96. // The result of this function should be the same as this:
  97. //RMMercatorPoint test = [self zoomPoint:origin ByFactor:1.0f / factor Near:pivot];
  98. // First we move the origin to the pivot...
  99. origin.easting += aPixelPoint.x * metersPerPixel;
  100. origin.northing += (screenBounds.size.height - aPixelPoint.y) * metersPerPixel;
  101. // Then scale by 1/factor
  102. metersPerPixel /= factor;
  103. // Then translate back
  104. origin.easting -= aPixelPoint.x * metersPerPixel;
  105. origin.northing -= (screenBounds.size.height - aPixelPoint.y) * metersPerPixel;
  106. origin = [projection wrapPointHorizontally:origin];
  107. //RMLog(@"test: %f %f", test.x, test.y);
  108. //RMLog(@"correct: %f %f", origin.easting, origin.y);
  109. // CGPoint p = [self projectMercatorPoint:[self projectScreenPointToMercator:CGPointZero]];
  110. // RMLog(@"origin at %f %f", p.x, p.y);
  111. // CGPoint q = [self projectMercatorPoint:[self projectScreenPointToMercator:CGPointMake(100,100)]];
  112. // RMLog(@"100 100 at %f %f", q.x, q.y);
  113. }
  114. - (void)zoomBy: (float) factor
  115. {
  116. metersPerPixel *= factor;
  117. }
  118. /*
  119. This method returns the pixel point based on the currently displayed map view converted from a RMProjectedPoint.
  120. origin is the top left projected point currently displayed in the view. The range of this value is based
  121. on the planetBounds. planetBounds in turn is based on an RMProjection. For example
  122. look at +(RMProjection*)googleProjection to see range of values for planetBounds/origin.
  123. The tricky part is when the current map view contains the divider for horizontally wrapping maps.
  124. Note: tested only with googleProjection
  125. */
  126. - (CGPoint) projectXYPoint:(RMProjectedPoint)aPoint withMetersPerPixel:(float)aScale
  127. {
  128. CGPoint aPixelPoint = { 0, 0 };
  129. RMProjectedRect projectedScreenBounds;
  130. projectedScreenBounds.origin = origin;
  131. projectedScreenBounds.size.width = screenBounds.size.width * aScale;
  132. projectedScreenBounds.size.height = screenBounds.size.height * aScale;
  133. RMProjectedRect planetBounds = [projection planetBounds];
  134. RMProjectedPoint planetEndPoint = {planetBounds.origin.easting + planetBounds.size.width,
  135. planetBounds.origin.northing + planetBounds.size.height};
  136. // Normalize coordinate system so there is no negative values
  137. RMProjectedRect normalizedProjectedScreenBounds;
  138. normalizedProjectedScreenBounds.origin.easting = projectedScreenBounds.origin.easting + planetEndPoint.easting;
  139. normalizedProjectedScreenBounds.origin.northing = projectedScreenBounds.origin.northing + planetEndPoint.northing;
  140. normalizedProjectedScreenBounds.size = projectedScreenBounds.size;
  141. RMProjectedPoint normalizedProjectedPoint;
  142. normalizedProjectedPoint.easting = aPoint.easting + planetEndPoint.easting;
  143. normalizedProjectedPoint.northing = aPoint.northing + planetEndPoint.northing;
  144. double rightMostViewableEasting;
  145. // check if world wrap divider is contained in view
  146. if (( normalizedProjectedScreenBounds.origin.easting + normalizedProjectedScreenBounds.size.width ) > planetBounds.size.width ) {
  147. rightMostViewableEasting = projectedScreenBounds.size.width - ( planetBounds.size.width - normalizedProjectedScreenBounds.origin.easting );
  148. // Check if Right of divider but on screen still
  149. if ( normalizedProjectedPoint.easting <= rightMostViewableEasting ) {
  150. aPixelPoint.x = ( planetBounds.size.width + normalizedProjectedPoint.easting - normalizedProjectedScreenBounds.origin.easting ) / aScale;
  151. } else {
  152. // everywhere else is left of divider
  153. aPixelPoint.x = ( normalizedProjectedPoint.easting - normalizedProjectedScreenBounds.origin.easting ) / aScale;
  154. }
  155. }
  156. else {
  157. // Divider not contained in view
  158. aPixelPoint.x = ( normalizedProjectedPoint.easting - normalizedProjectedScreenBounds.origin.easting ) / aScale;
  159. }
  160. aPixelPoint.y = screenBounds.size.height - ( normalizedProjectedPoint.northing - normalizedProjectedScreenBounds.origin.northing ) / aScale;
  161. return aPixelPoint;
  162. }
  163. /*
  164. - (CGPoint) projectXYPoint:(RMProjectedPoint)aPoint withMetersPerPixel:(float)aScale
  165. {
  166. CGPoint aPixelPoint;
  167. CGFloat originX = origin.easting;
  168. CGFloat boundsWidth = [projection planetBounds].size.width;
  169. CGFloat pointX = aPoint.easting - boundsWidth/2;
  170. CGFloat left = sqrt((pointX - (originX - boundsWidth))*(pointX - (originX - boundsWidth)));
  171. CGFloat middle = sqrt((pointX - originX)*(pointX - originX));
  172. CGFloat right = sqrt((pointX - (originX + boundsWidth))*(pointX - (originX + boundsWidth)));
  173. //RMLog(@"left:%f middle:%f right:%f x:%f width:%f", left, middle, right, pointX, boundsWidth);//LK
  174. if(middle <= left && middle <= right){
  175. aPixelPoint.x = (aPoint.easting - originX) / aScale;
  176. } else if(left <= middle && left <= right){
  177. //RMLog(@"warning: projectXYPoint middle..");//LK
  178. aPixelPoint.x = (aPoint.easting - (originX)) / aScale;
  179. } else{ //right
  180. aPixelPoint.x = (aPoint.easting - (originX+boundsWidth)) / aScale;
  181. }
  182. aPixelPoint.y = screenBounds.size.height - (aPoint.northing - origin.northing) / aScale;
  183. return aPixelPoint;
  184. }
  185. */
  186. - (CGPoint) projectXYPoint: (RMProjectedPoint)aPoint
  187. {
  188. return [self projectXYPoint:aPoint withMetersPerPixel:metersPerPixel];
  189. }
  190. - (CGRect) projectXYRect: (RMProjectedRect) aRect
  191. {
  192. CGRect aPixelRect;
  193. aPixelRect.origin = [self projectXYPoint: aRect.origin];
  194. aPixelRect.size.width = aRect.size.width / metersPerPixel;
  195. aPixelRect.size.height = aRect.size.height / metersPerPixel;
  196. return aPixelRect;
  197. }
  198. - (RMProjectedPoint)projectScreenPointToXY: (CGPoint) aPixelPoint withMetersPerPixel:(float)aScale
  199. {
  200. RMProjectedPoint aPoint;
  201. aPoint.easting = origin.easting + aPixelPoint.x * aScale;
  202. aPoint.northing = origin.northing + (screenBounds.size.height - aPixelPoint.y) * aScale;
  203. origin = [projection wrapPointHorizontally:origin];
  204. return aPoint;
  205. }
  206. - (RMProjectedPoint) projectScreenPointToXY: (CGPoint) aPixelPoint
  207. {
  208. // I will assume the point is within the screenbounds rectangle.
  209. return [projection wrapPointHorizontally:[self projectScreenPointToXY:aPixelPoint withMetersPerPixel:metersPerPixel]];
  210. }
  211. - (RMProjectedRect) projectScreenRectToXY: (CGRect) aPixelRect
  212. {
  213. RMProjectedRect aRect;
  214. aRect.origin = [self projectScreenPointToXY: aPixelRect.origin];
  215. aRect.size.width = aPixelRect.size.width * metersPerPixel;
  216. aRect.size.height = aPixelRect.size.height * metersPerPixel;
  217. return aRect;
  218. }
  219. - (RMProjectedSize)projectScreenSizeToXY: (CGSize) aPixelSize
  220. {
  221. RMProjectedSize aSize;
  222. aSize.width = aPixelSize.width * metersPerPixel;
  223. aSize.height = -aPixelSize.height * metersPerPixel;
  224. return aSize;
  225. }
  226. - (RMProjectedRect) projectedBounds
  227. {
  228. RMProjectedRect aRect;
  229. aRect.origin = origin;
  230. aRect.size.width = screenBounds.size.width * metersPerPixel;
  231. aRect.size.height = screenBounds.size.height * metersPerPixel;
  232. return aRect;
  233. }
  234. -(void) setProjectedBounds: (RMProjectedRect) aRect
  235. {
  236. float scaleX = aRect.size.width / screenBounds.size.width;
  237. float scaleY = aRect.size.height / screenBounds.size.height;
  238. // I will pick a scale in between those two.
  239. metersPerPixel = (scaleX + scaleY) / 2;
  240. origin = [projection wrapPointHorizontally:aRect.origin];
  241. }
  242. - (RMProjectedPoint) projectedCenter
  243. {
  244. RMProjectedPoint aPoint;
  245. aPoint.easting = origin.easting + screenBounds.size.width * metersPerPixel / 2;
  246. aPoint.northing = origin.northing + screenBounds.size.height * metersPerPixel / 2;
  247. aPoint = [projection wrapPointHorizontally:aPoint];
  248. return aPoint;
  249. }
  250. - (void) setProjectedCenter: (RMProjectedPoint) aPoint
  251. {
  252. origin = [projection wrapPointHorizontally:aPoint];
  253. origin.easting -= screenBounds.size.width * metersPerPixel / 2;
  254. origin.northing -= screenBounds.size.height * metersPerPixel / 2;
  255. }
  256. - (void) setScreenBounds:(CGRect)rect;
  257. {
  258. screenBounds = rect;
  259. }
  260. -(CGRect) screenBounds
  261. {
  262. return screenBounds;
  263. }
  264. -(float) metersPerPixel
  265. {
  266. return metersPerPixel;
  267. }
  268. -(void) setMetersPerPixel: (float) newMPP
  269. {
  270. // We need to adjust the origin - since the origin
  271. // is in the corner, it will change when we change the scale.
  272. RMProjectedPoint center = [self projectedCenter];
  273. metersPerPixel = newMPP;
  274. [self setProjectedCenter:center];
  275. }
  276. @end