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