PageRenderTime 18ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/MapView/Map/RMPath.m

http://github.com/route-me/route-me
Objective C | 419 lines | 313 code | 72 blank | 34 comment | 56 complexity | f8331829e1c7c352affbc532f9a047c4 MD5 | raw file
  1. ///
  2. // RMPath.m
  3. //
  4. // Copyright (c) 2008-2010, 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 "RMPath.h"
  28. #import "RMMapView.h"
  29. #import "RMMapContents.h"
  30. #import "RMMercatorToScreenProjection.h"
  31. #import "RMPixel.h"
  32. #import "RMProjection.h"
  33. #import "RMNotifications.h"
  34. @interface RMPath () {
  35. RMProjectedPoint projectedLocation;
  36. CGFloat *_lineDashLengths;
  37. CGFloat *_scaledLineDashLengths;
  38. size_t _lineDashCount;
  39. CGFloat lineDashPhase;
  40. CGMutablePathRef path;
  41. RMMapContents *mapContents;
  42. CGRect originalContentsRect;
  43. BOOL redrawPending;
  44. }
  45. - (void)addPointToXY:(RMProjectedPoint) point withDrawing:(BOOL)isDrawing;
  46. - (void)recalculateGeometry;
  47. @end
  48. @implementation RMPath
  49. @synthesize lineCap, lineJoin, lineWidth, lineColor, fillColor, scaleLineWidth, shadowBlur, shadowOffset, shadowColor, lineDashPhase, lineDashLengths, scaleLineDash, projectedLocation, enableDragging, enableRotation;
  50. @dynamic CGPath, projectedBounds;
  51. - (id) initWithContents: (RMMapContents*)aContents {
  52. if (![super init]) return nil;
  53. mapContents = aContents;
  54. path = CGPathCreateMutable();
  55. // Defaults
  56. lineWidth = 4.0;
  57. lineCap = kCGLineCapRound;
  58. lineJoin = kCGLineJoinRound;
  59. scaleLineWidth = NO;
  60. enableDragging = YES;
  61. enableRotation = YES;
  62. self.lineColor = [UIColor blackColor];
  63. scaleLineDash = NO;
  64. _lineDashCount = 0;
  65. _lineDashLengths = NULL;
  66. _scaledLineDashLengths = NULL;
  67. lineDashPhase = 0.0;
  68. shadowBlur = 0.0;
  69. shadowOffset = CGSizeMake(0, 0);
  70. self.shadowColor = [UIColor clearColor];
  71. self.masksToBounds = YES;
  72. if ( [self respondsToSelector:@selector(setContentsScale:)] ) self.contentsScale = ([[UIScreen mainScreen] respondsToSelector:@selector(scale)] ? [UIScreen mainScreen].scale : 1.0);
  73. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resumeExpensiveOperationsNotification:) name:RMResumeExpensiveOperations object:nil];
  74. return self;
  75. }
  76. - (id) initForMap: (RMMapView*)map {
  77. return [self initWithContents:[map contents]];
  78. }
  79. - (id) initForMap: (RMMapView*)map withCoordinates:(const CLLocationCoordinate2D*)coordinates count:(NSInteger)count {
  80. if ( !(self = [self initWithContents:[map contents]]) ) return nil;
  81. [self moveToLatLong:coordinates[0]];
  82. for ( NSInteger i=1; i<count; i++ ) {
  83. [self addLineToLatLong:coordinates[i]];
  84. }
  85. return self;
  86. }
  87. -(void) dealloc {
  88. [[NSNotificationCenter defaultCenter] removeObserver:self];
  89. CGPathRelease(path);
  90. [lineColor release];
  91. [fillColor release];
  92. [shadowColor release];
  93. if ( _lineDashLengths ) free(_lineDashLengths);
  94. [super dealloc];
  95. }
  96. - (id<CAAction>)actionForKey:(NSString *)key {
  97. return nil;
  98. }
  99. - (void) moveToXY: (RMProjectedPoint) point {
  100. [self addPointToXY: point withDrawing: FALSE];
  101. }
  102. - (void) moveToScreenPoint: (CGPoint) point {
  103. [self moveToXY: [[mapContents mercatorToScreenProjection] projectScreenPointToXY: point]];
  104. }
  105. - (void) moveToLatLong: (RMLatLong) point {
  106. [self moveToXY:[[mapContents projection] latLongToPoint:point]];
  107. }
  108. - (void) addLineToXY: (RMProjectedPoint) point {
  109. [self addPointToXY: point withDrawing: TRUE];
  110. }
  111. - (void) addLineToScreenPoint: (CGPoint) point {
  112. [self addLineToXY: [[mapContents mercatorToScreenProjection] projectScreenPointToXY: point]];
  113. }
  114. - (void) addLineToLatLong: (RMLatLong) point{
  115. [self addLineToXY:[[mapContents projection] latLongToPoint:point]];
  116. }
  117. - (void) closePath {
  118. CGPathCloseSubpath(path);
  119. }
  120. - (void) setLineWidth: (float) newLineWidth {
  121. lineWidth = newLineWidth;
  122. [self recalculateGeometry];
  123. }
  124. - (NSArray *)lineDashLengths {
  125. NSMutableArray *lengths = [NSMutableArray arrayWithCapacity:_lineDashCount];
  126. for(size_t dashIndex=0; dashIndex<_lineDashCount; dashIndex++){
  127. [lengths addObject:(id)[NSNumber numberWithFloat:_lineDashLengths[dashIndex]]];
  128. }
  129. return lengths;
  130. }
  131. - (void) setLineDashLengths:(NSArray *)lengths {
  132. if(_lineDashLengths){
  133. free(_lineDashLengths);
  134. _lineDashLengths = NULL;
  135. }
  136. if(_scaledLineDashLengths){
  137. free(_scaledLineDashLengths);
  138. _scaledLineDashLengths = NULL;
  139. }
  140. _lineDashCount = [lengths count];
  141. if(!_lineDashCount){
  142. return;
  143. }
  144. _lineDashLengths = calloc(_lineDashCount, sizeof(CGFloat));
  145. if(!scaleLineDash){
  146. _scaledLineDashLengths = calloc(_lineDashCount, sizeof(CGFloat));
  147. }
  148. NSEnumerator *lengthEnumerator = [lengths objectEnumerator];
  149. id lenObj;
  150. size_t dashIndex = 0;
  151. while ((lenObj = [lengthEnumerator nextObject])) {
  152. if([lenObj isKindOfClass: [NSNumber class]]){
  153. _lineDashLengths[dashIndex] = [lenObj floatValue];
  154. } else {
  155. _lineDashLengths[dashIndex] = 0.0;
  156. }
  157. dashIndex++;
  158. }
  159. }
  160. -(void)setLineCap:(CGLineCap)theLineCap {
  161. if ( theLineCap != lineCap ) {
  162. lineCap = theLineCap;
  163. [self setNeedsDisplay];
  164. }
  165. }
  166. -(void)setLineJoin:(CGLineJoin)theLineJoin {
  167. if ( theLineJoin != lineJoin ) {
  168. lineJoin = theLineJoin;
  169. [self setNeedsDisplay];
  170. }
  171. }
  172. - (void)setLineColor:(UIColor *)aLineColor {
  173. if (lineColor != aLineColor) {
  174. [lineColor release];
  175. lineColor = [aLineColor retain];
  176. [self setNeedsDisplay];
  177. }
  178. }
  179. - (void)setFillColor:(UIColor *)aFillColor {
  180. if (fillColor != aFillColor) {
  181. [fillColor release];
  182. fillColor = [aFillColor retain];
  183. [self setNeedsDisplay];
  184. }
  185. }
  186. -(void)setShadowBlur:(CGFloat)theShadowBlur {
  187. if ( shadowBlur != theShadowBlur ) {
  188. shadowBlur = theShadowBlur;
  189. [self setNeedsDisplay];
  190. }
  191. }
  192. -(void)setShadowOffset:(CGSize)theShadowOffset {
  193. if ( !CGSizeEqualToSize(shadowOffset, theShadowOffset) ) {
  194. shadowOffset = theShadowOffset;
  195. [self setNeedsDisplay];
  196. }
  197. }
  198. -(void)setShadowColor:(UIColor *)theShadowColor {
  199. if ( ![shadowColor isEqual:theShadowColor] ) {
  200. [theShadowColor retain];
  201. [shadowColor release];
  202. shadowColor = theShadowColor;
  203. [self setNeedsDisplay];
  204. }
  205. }
  206. - (void)moveBy: (CGSize) delta {
  207. if(enableDragging){
  208. [super moveBy:delta];
  209. }
  210. }
  211. - (void)setPosition:(CGPoint)value {
  212. [super setPosition:value];
  213. [self recalculateGeometry];
  214. }
  215. - (void)addPointToXY:(RMProjectedPoint) point withDrawing:(BOOL)isDrawing {
  216. if ( CGPathIsEmpty(path) ) {
  217. projectedLocation = point;
  218. self.position = [[mapContents mercatorToScreenProjection] projectXYPoint:projectedLocation];
  219. CGPathMoveToPoint(path, NULL, 0.0f, 0.0f);
  220. } else {
  221. point.easting = point.easting - projectedLocation.easting;
  222. point.northing = point.northing - projectedLocation.northing;
  223. if ( isDrawing ) {
  224. CGPathAddLineToPoint(path, NULL, point.easting, point.northing);
  225. } else {
  226. CGPathMoveToPoint(path, NULL, point.easting, point.northing);
  227. }
  228. [self recalculateGeometry];
  229. }
  230. [self setNeedsDisplay];
  231. }
  232. - (void)recalculateGeometry {
  233. RMMercatorToScreenProjection *projection = [mapContents mercatorToScreenProjection];
  234. float scaledLineWidth = lineWidth;
  235. if ( !scaleLineWidth ) {
  236. scaledLineWidth *= [mapContents metersPerPixel];
  237. }
  238. // Get path dimensions (in mercators relative to projectedLocation; bounding box origin is bottom-left due to flipped coord system)
  239. CGRect pathDimensions = CGRectInset(CGPathGetBoundingBox(path), -scaledLineWidth, -scaledLineWidth);
  240. // Convert bounding box to pixels in Quartz coord space, relative to projectedLocation in Quartz coord space
  241. CGRect pixelBounds = RMScaleCGRectAboutPoint(CGRectMake(pathDimensions.origin.x,
  242. -pathDimensions.origin.y - pathDimensions.size.height,
  243. pathDimensions.size.width,
  244. pathDimensions.size.height),
  245. 1.0f / [projection metersPerPixel], CGPointZero);
  246. // Clip bound rect to screen bounds.
  247. // If bounds are not clipped, they won't display when you zoom in too much.
  248. CGRect screenBounds = [mapContents screenBounds];
  249. CGPoint myPosition = [projection projectXYPoint: projectedLocation];
  250. CGRect clippedBounds = pixelBounds;
  251. CGFloat outset = MAX(screenBounds.size.width, screenBounds.size.height);
  252. clippedBounds.origin.x += myPosition.x; clippedBounds.origin.y += myPosition.y;
  253. clippedBounds = CGRectIntersection(clippedBounds, CGRectInset(screenBounds, -outset, -outset));
  254. clippedBounds.origin.x -= myPosition.x; clippedBounds.origin.y -= myPosition.y;
  255. BOOL clipped = !CGRectEqualToRect(clippedBounds, pixelBounds);
  256. CGRect contentsRect = CGRectZero;
  257. if ( pixelBounds.size.height > 0 && pixelBounds.size.width > 0 ) {
  258. contentsRect = CGRectMake((clippedBounds.origin.x - pixelBounds.origin.x) / pixelBounds.size.width,
  259. (clippedBounds.origin.y - pixelBounds.origin.y) / pixelBounds.size.height,
  260. clippedBounds.size.width / pixelBounds.size.width,
  261. clippedBounds.size.height / pixelBounds.size.height);
  262. if ( ![RMMapContents performExpensiveOperations] ) {
  263. // While moving, just adjust the contents rect instead of redrawing
  264. if ( clippedBounds.size.width > 0 && clippedBounds.size.height > 0 ) {
  265. // Select a contents rect that is proportonal to the currently drawn region (which may be a subset of the total path bounds)
  266. self.contentsRect = CGRectMake((contentsRect.origin.x - originalContentsRect.origin.x) / originalContentsRect.size.width,
  267. (contentsRect.origin.y - originalContentsRect.origin.y) / originalContentsRect.size.height,
  268. contentsRect.size.width / originalContentsRect.size.width,
  269. contentsRect.size.height / originalContentsRect.size.height);
  270. }
  271. } else {
  272. originalContentsRect = contentsRect;
  273. }
  274. }
  275. pixelBounds = clippedBounds;
  276. if ( pixelBounds.size.width > 0 && pixelBounds.size.height > 0 ) {
  277. self.anchorPoint = CGPointMake(-pixelBounds.origin.x / pixelBounds.size.width, -pixelBounds.origin.y / pixelBounds.size.height);
  278. }
  279. CGRect priorBounds = self.bounds;
  280. self.bounds = pixelBounds;
  281. [super setPosition:myPosition];
  282. if ( redrawPending || fabs(priorBounds.size.width - pixelBounds.size.width) > 1.0 || fabs(priorBounds.size.height - pixelBounds.size.height) > 1.0 || clipped ) {
  283. // Redraw if we changed size, clipped the view, or if we were pending a redraw
  284. if ( [RMMapContents performExpensiveOperations] ) {
  285. redrawPending = NO;
  286. self.contentsRect = CGRectMake(0, 0, 1, 1);
  287. [self setNeedsDisplay];
  288. } else {
  289. redrawPending = YES;
  290. }
  291. }
  292. }
  293. - (void)drawInContext:(CGContextRef)theContext {
  294. float scale = 1.0f / [mapContents metersPerPixel];
  295. float scaledLineWidth = lineWidth;
  296. if ( !scaleLineWidth ) {
  297. scaledLineWidth *= [mapContents metersPerPixel];
  298. }
  299. CGFloat *dashLengths = _lineDashLengths;
  300. if(!scaleLineDash && _lineDashLengths) {
  301. dashLengths = _scaledLineDashLengths;
  302. for(size_t dashIndex=0; dashIndex<_lineDashCount; dashIndex++){
  303. dashLengths[dashIndex] = _lineDashLengths[dashIndex] * scale;
  304. }
  305. }
  306. CGContextScaleCTM(theContext, scale, -scale); // Flip vertically, as path is in projected coord space with origin with y axis increasing upwards
  307. CGContextBeginPath(theContext);
  308. CGContextAddPath(theContext, path);
  309. CGContextSetLineWidth(theContext, scaledLineWidth);
  310. CGContextSetLineCap(theContext, lineCap);
  311. CGContextSetLineJoin(theContext, lineJoin);
  312. if(_lineDashLengths){
  313. CGContextSetLineDash(theContext, lineDashPhase, dashLengths, _lineDashCount);
  314. }
  315. if ( lineColor ) {
  316. CGContextSetStrokeColorWithColor(theContext, [lineColor CGColor]);
  317. }
  318. if ( ![shadowColor isEqual:[UIColor clearColor]] ) {
  319. CGContextSetShadowWithColor(theContext, shadowOffset, shadowBlur, [shadowColor CGColor]);
  320. }
  321. if ( fillColor ) {
  322. CGContextSetFillColorWithColor(theContext, [fillColor CGColor]);
  323. }
  324. CGContextDrawPath(theContext, (lineColor && fillColor ? kCGPathFillStroke : (lineColor ? kCGPathStroke : kCGPathFill)));
  325. }
  326. - (CGPathRef)CGPath {
  327. return path;
  328. }
  329. - (RMProjectedRect)projectedBounds {
  330. float scaledLineWidth = lineWidth;
  331. if ( !scaleLineWidth ) {
  332. scaledLineWidth *= [mapContents metersPerPixel];
  333. }
  334. CGRect regionRect = CGRectInset(CGPathGetBoundingBox(path), -scaledLineWidth, -scaledLineWidth);
  335. return RMMakeProjectedRect(regionRect.origin.x + projectedLocation.easting,
  336. regionRect.origin.y + projectedLocation.northing,
  337. regionRect.size.width,
  338. regionRect.size.height);
  339. }
  340. - (void)resumeExpensiveOperationsNotification:(NSNotification*)notification {
  341. if ( redrawPending ) {
  342. self.contentsRect = CGRectMake(0, 0, 1, 1);
  343. [self recalculateGeometry];
  344. redrawPending = NO;
  345. }
  346. }
  347. @end