PageRenderTime 76ms CodeModel.GetById 8ms app.highlight 64ms RepoModel.GetById 1ms app.codeStats 1ms

/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
 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