From e0fb7ec2d26c6cbc646a8f7dde9bae47c16cd08f Mon Sep 17 00:00:00 2001 From: Thomas Rasch <thomas@fook.de> Date: Wed, 20 Jun 2012 17:24:01 +0200 Subject: [PATCH] o Added the RMShape implementation from @plancalculus which will replace RMPath (addresses #29) --- MapView/Map/RMMapScrollView.h | 4 ++-- MapView/Map/RMMapScrollView.m | 14 +++++++------- MapView/Map/RMMapView.h | 4 ++-- MapView/Map/RMMapView.m | 36 +++++++++++++++++++++++++++++------- MapView/Map/RMShape.h | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ MapView/Map/RMShape.m | 444 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ MapView/MapView.xcodeproj/project.pbxproj | 8 ++++++++ 7 files changed, 574 insertions(+), 18 deletions(-) create mode 100644 MapView/Map/RMShape.h create mode 100644 MapView/Map/RMShape.m diff --git a/MapView/Map/RMMapScrollView.h b/MapView/Map/RMMapScrollView.h index 6fc2023..b81bc7f 100644 --- a/MapView/Map/RMMapScrollView.h +++ b/MapView/Map/RMMapScrollView.h @@ -10,7 +10,7 @@ @class RMMapScrollView; -@protocol UIScrollViewConstraintsDelegate <NSObject> +@protocol RMMapScrollViewDelegate <NSObject> - (CGPoint)scrollView:(RMMapScrollView *)aScrollView correctedOffsetForContentOffset:(CGPoint)aContentOffset; - (CGSize)scrollView:(RMMapScrollView *)aScrollView correctedSizeForContentSize:(CGSize)aContentSize; @@ -19,6 +19,6 @@ @interface RMMapScrollView : UIScrollView -@property (nonatomic, assign) id <UIScrollViewConstraintsDelegate> constraintsDelegate; +@property (nonatomic, assign) id <RMMapScrollViewDelegate> mapScrollViewDelegate; @end diff --git a/MapView/Map/RMMapScrollView.m b/MapView/Map/RMMapScrollView.m index c4040ea..d62fa09 100644 --- a/MapView/Map/RMMapScrollView.m +++ b/MapView/Map/RMMapScrollView.m @@ -10,28 +10,28 @@ @implementation RMMapScrollView -@synthesize constraintsDelegate; +@synthesize mapScrollViewDelegate; - (void)setContentOffset:(CGPoint)contentOffset { - if (self.constraintsDelegate) - contentOffset = [self.constraintsDelegate scrollView:self correctedOffsetForContentOffset:contentOffset]; + if (self.mapScrollViewDelegate) + contentOffset = [self.mapScrollViewDelegate scrollView:self correctedOffsetForContentOffset:contentOffset]; [super setContentOffset:contentOffset]; } - (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated { - if (self.constraintsDelegate) - contentOffset = [self.constraintsDelegate scrollView:self correctedOffsetForContentOffset:contentOffset]; + if (self.mapScrollViewDelegate) + contentOffset = [self.mapScrollViewDelegate scrollView:self correctedOffsetForContentOffset:contentOffset]; [super setContentOffset:contentOffset animated:animated]; } - (void)setContentSize:(CGSize)contentSize { - if (self.constraintsDelegate) - contentSize = [self.constraintsDelegate scrollView:self correctedSizeForContentSize:contentSize]; + if (self.mapScrollViewDelegate) + contentSize = [self.mapScrollViewDelegate scrollView:self correctedSizeForContentSize:contentSize]; [super setContentSize:contentSize]; } diff --git a/MapView/Map/RMMapView.h b/MapView/Map/RMMapView.h index fdfc614..ee71cfc 100644 --- a/MapView/Map/RMMapView.h +++ b/MapView/Map/RMMapView.h @@ -62,10 +62,10 @@ typedef enum { @protocol RMMercatorToTileProjection; @protocol RMTileSource; -@protocol UIScrollViewConstraintsDelegate; +@protocol RMMapScrollViewDelegate; @interface RMMapView : UIView <UIScrollViewDelegate, RMMapOverlayViewDelegate, - RMMapTiledLayerViewDelegate, UIScrollViewConstraintsDelegate> + RMMapTiledLayerViewDelegate, RMMapScrollViewDelegate> { id <RMMapViewDelegate> delegate; diff --git a/MapView/Map/RMMapView.m b/MapView/Map/RMMapView.m index 1e900c8..4289a1e 100644 --- a/MapView/Map/RMMapView.m +++ b/MapView/Map/RMMapView.m @@ -33,6 +33,7 @@ #import "RMProjection.h" #import "RMMarker.h" #import "RMPath.h" +#import "RMShape.h" #import "RMAnnotation.h" #import "RMQuadTree.h" @@ -101,6 +102,7 @@ float _lastZoom; CGPoint _lastContentOffset, _accumulatedDelta; + CGSize _lastContentSize; BOOL _mapScrollViewIsZooming; BOOL _enableDragging, _enableBouncing; @@ -465,7 +467,7 @@ _constrainMovement = YES; _constrainingProjectedBounds = RMProjectedRectMake(southWest.x, southWest.y, northEast.x - southWest.x, northEast.y - southWest.y); - mapScrollView.constraintsDelegate = self; + mapScrollView.mapScrollViewDelegate = self; } #pragma mark - @@ -976,10 +978,10 @@ _lastZoom = [self zoom]; _lastContentOffset = mapScrollView.contentOffset; _accumulatedDelta = CGPointMake(0.0, 0.0); + _lastContentSize = mapScrollView.contentSize; [mapScrollView addObserver:self forKeyPath:@"contentOffset" options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:NULL]; - if (_constrainMovement) - mapScrollView.constraintsDelegate = self; + mapScrollView.mapScrollViewDelegate = self; mapScrollView.zoomScale = exp2f([self zoom]); @@ -1240,8 +1242,27 @@ NSValue *oldValue = [change objectForKey:NSKeyValueChangeOldKey], *newValue = [change objectForKey:NSKeyValueChangeNewKey]; - if (CGPointEqualToPoint([oldValue CGPointValue], [newValue CGPointValue])) + CGPoint oldContentOffset = [oldValue CGPointValue], + newContentOffset = [newValue CGPointValue]; + + if (CGPointEqualToPoint(oldContentOffset, newContentOffset)) + return; + + // The first offset during zooming out (animated) is always garbage + if (_mapScrollViewIsZooming == YES && + mapScrollView.zooming == NO && + _lastContentSize.width > mapScrollView.contentSize.width && + (newContentOffset.y - oldContentOffset.y) == 0.0) + { + _lastContentOffset = mapScrollView.contentOffset; + _lastContentSize = mapScrollView.contentSize; + return; + } + +// RMLog(@"contentOffset: {%.0f,%.0f} -> {%.1f,%.1f} (%.0f,%.0f)", oldContentOffset.x, oldContentOffset.y, newContentOffset.x, newContentOffset.y, newContentOffset.x - oldContentOffset.x, newContentOffset.y - oldContentOffset.y); +// RMLog(@"contentSize: {%.0f,%.0f} -> {%.0f,%.0f}", _lastContentSize.width, _lastContentSize.height, mapScrollView.contentSize.width, mapScrollView.contentSize.height); +// RMLog(@"isZooming: %d, scrollview.zooming: %d", _mapScrollViewIsZooming, mapScrollView.zooming); RMProjectedRect planetBounds = projection.planetBounds; metersPerPixel = planetBounds.size.width / mapScrollView.contentSize.width; @@ -1274,11 +1295,12 @@ } else { - [self correctPositionOfAllAnnotationsIncludingInvisibles:NO animated:YES]; + [self correctPositionOfAllAnnotationsIncludingInvisibles:NO animated:(_mapScrollViewIsZooming && !mapScrollView.zooming)]; _lastZoom = zoom; } _lastContentOffset = mapScrollView.contentOffset; + _lastContentSize = mapScrollView.contentSize; // Don't do anything stupid here or your scrolling experience will suck if (_delegateHasMapViewRegionDidChange) @@ -1702,9 +1724,9 @@ CGPoint newPosition = CGPointMake((normalizedProjectedPoint.x / metersPerPixel) - mapScrollView.contentOffset.x, mapScrollView.contentSize.height - (normalizedProjectedPoint.y / metersPerPixel) - mapScrollView.contentOffset.y); - [annotation setPosition:newPosition animated:animated]; - // RMLog(@"Change annotation at {%f,%f} in mapView {%f,%f}", annotation.position.x, annotation.position.y, mapScrollView.contentSize.width, mapScrollView.contentSize.height); + + [annotation setPosition:newPosition animated:animated]; } - (void)correctPositionOfAllAnnotationsIncludingInvisibles:(BOOL)correctAllAnnotations animated:(BOOL)animated diff --git a/MapView/Map/RMShape.h b/MapView/Map/RMShape.h new file mode 100644 index 0000000..b6d0e80 --- /dev/null +++ b/MapView/Map/RMShape.h @@ -0,0 +1,82 @@ +// +// RMShape.h +// +// Copyright (c) 2008-2012, Route-Me Contributors +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#import <UIKit/UIKit.h> + +#import "RMFoundation.h" +#import "RMMapLayer.h" + +@class RMMapView; + +@interface RMShape : RMMapLayer +{ + CGRect pathBoundingBox; + + /// Width of the line, in pixels + float lineWidth; + + // Line dash style + NSArray *lineDashLengths; + CGFloat lineDashPhase; + + BOOL scaleLineWidth; + BOOL scaleLineDash; // if YES line dashes will be scaled to keep a constant size if the layer is zoomed +} + +- (id)initWithView:(RMMapView *)aMapView; + +@property (nonatomic, retain) NSString *fillRule; +@property (nonatomic, retain) NSString *lineCap; +@property (nonatomic, retain) NSString *lineJoin; +@property (nonatomic, retain) UIColor *lineColor; +@property (nonatomic, retain) UIColor *fillColor; + +@property (nonatomic, assign) NSArray *lineDashLengths; +@property (nonatomic, assign) CGFloat lineDashPhase; +@property (nonatomic, assign) BOOL scaleLineDash; +@property (nonatomic, assign) float lineWidth; +@property (nonatomic, assign) BOOL scaleLineWidth; + +@property (nonatomic, readonly) CGRect pathBoundingBox; + +- (void)moveToProjectedPoint:(RMProjectedPoint)projectedPoint; +- (void)moveToScreenPoint:(CGPoint)point; +- (void)moveToCoordinate:(CLLocationCoordinate2D)coordinate; + +- (void)addLineToProjectedPoint:(RMProjectedPoint)projectedPoint; +- (void)addLineToScreenPoint:(CGPoint)point; +- (void)addLineToCoordinate:(CLLocationCoordinate2D)coordinate; + +// Change the path without recalculating the geometry (performance!) +- (void)performBatchOperations:(void (^)(RMShape *aPath))block; + +/// This closes the path, connecting the last point to the first. +/// After this action, no further points can be added to the path. +/// There is no requirement that a path be closed. +- (void)closePath; + +@end diff --git a/MapView/Map/RMShape.m b/MapView/Map/RMShape.m new file mode 100644 index 0000000..8ed7d0f --- /dev/null +++ b/MapView/Map/RMShape.m @@ -0,0 +1,444 @@ +/// +// RMShape.m +// +// Copyright (c) 2008-2012, Route-Me Contributors +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#import "RMShape.h" +#import "RMPixel.h" +#import "RMProjection.h" +#import "RMMapView.h" +#import "RMAnnotation.h" + +@implementation RMShape +{ + BOOL isFirstPoint, ignorePathUpdates; + float lastScale; + + CGRect nonClippedBounds; + CGRect previousBounds; + + CAShapeLayer *shapeLayer; + UIBezierPath *bezierPath; + + RMMapView *mapView; +} + +@synthesize scaleLineWidth; +@synthesize lineDashLengths; +@synthesize scaleLineDash; +@synthesize pathBoundingBox; + +#define kDefaultLineWidth 2.0 + +- (id)initWithView:(RMMapView *)aMapView +{ + if (!(self = [super init])) + return nil; + + mapView = aMapView; + + bezierPath = [[UIBezierPath alloc] init]; + lineWidth = kDefaultLineWidth; + ignorePathUpdates = NO; + + shapeLayer = [[CAShapeLayer alloc] init]; + shapeLayer.rasterizationScale = [[UIScreen mainScreen] scale]; + shapeLayer.lineWidth = lineWidth; + shapeLayer.lineCap = kCALineCapButt; + shapeLayer.lineJoin = kCALineJoinMiter; + shapeLayer.strokeColor = [UIColor blackColor].CGColor; + shapeLayer.fillColor = [UIColor clearColor].CGColor; + [self addSublayer:shapeLayer]; + + pathBoundingBox = CGRectZero; + nonClippedBounds = CGRectZero; + previousBounds = CGRectZero; + lastScale = 0.0; + + self.masksToBounds = YES; + + scaleLineWidth = NO; + scaleLineDash = NO; + isFirstPoint = YES; + + [(id)self setValue:[[UIScreen mainScreen] valueForKey:@"scale"] forKey:@"contentsScale"]; + + return self; +} + +- (void)dealloc +{ + mapView = nil; + [bezierPath release]; bezierPath = nil; + [shapeLayer release]; shapeLayer = nil; + [super dealloc]; +} + +- (id <CAAction>)actionForKey:(NSString *)key +{ + return nil; +} + +#pragma mark - + +- (void)recalculateGeometryAnimated:(BOOL)animated +{ + if (ignorePathUpdates) + return; + + float scale = 1.0f / [mapView metersPerPixel]; + + // we have to calculate the scaledLineWidth even if scalling did not change + // as the lineWidth might have changed + float scaledLineWidth; + + if (scaleLineWidth) + scaledLineWidth = lineWidth * scale; + else + scaledLineWidth = lineWidth; + + shapeLayer.lineWidth = scaledLineWidth; + + if (lineDashLengths) + { + if (scaleLineDash) + { + NSMutableArray *scaledLineDashLengths = [NSMutableArray array]; + + for (NSNumber *lineDashLength in lineDashLengths) + { + [scaledLineDashLengths addObject:[NSNumber numberWithFloat:lineDashLength.floatValue * scale]]; + } + + shapeLayer.lineDashPattern = scaledLineDashLengths; + } + else + { + shapeLayer.lineDashPattern = lineDashLengths; + } + } + + // we are about to overwrite nonClippedBounds, therefore we save the old value + CGRect previousNonClippedBounds = nonClippedBounds; + + if (scale != lastScale) + { + lastScale = scale; + + CGAffineTransform scaling = CGAffineTransformMakeScale(scale, scale); + UIBezierPath *scaledPath = [bezierPath copy]; + [scaledPath applyTransform:scaling]; + + if (animated) + { + CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"path"]; + animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + animation.repeatCount = 0; + animation.autoreverses = NO; + animation.fromValue = (id) shapeLayer.path; + animation.toValue = (id) scaledPath.CGPath; + [shapeLayer addAnimation:animation forKey:@"animatePath"]; + } + + shapeLayer.path = scaledPath.CGPath; + + // calculate the bounds of the scaled path + CGRect boundsInMercators = scaledPath.bounds; + nonClippedBounds = CGRectInset(boundsInMercators, -scaledLineWidth, -scaledLineWidth); + + [scaledPath release]; + } + + // if the path is not scaled, nonClippedBounds stay the same as in the previous invokation + + // Clip bound rect to screen bounds. + // If bounds are not clipped, they won't display when you zoom in too much. + + CGRect screenBounds = [mapView frame]; + + // we start with the non-clipped bounds and clip them + CGRect clippedBounds = nonClippedBounds; + + float offset; + const float outset = 150.0f; // provides a buffer off screen edges for when path is scaled or moved + + CGPoint newPosition = self.annotation.position; + +// RMLog(@"x:%f y:%f screen bounds: %f %f %f %f", newPosition.x, newPosition.y, screenBounds.origin.x, screenBounds.origin.y, screenBounds.size.width, screenBounds.size.height); + + // Clip top + offset = newPosition.y + clippedBounds.origin.y - screenBounds.origin.y + outset; + if (offset < 0.0f) + { + clippedBounds.origin.y -= offset; + clippedBounds.size.height += offset; + } + + // Clip left + offset = newPosition.x + clippedBounds.origin.x - screenBounds.origin.x + outset; + if (offset < 0.0f) + { + clippedBounds.origin.x -= offset; + clippedBounds.size.width += offset; + } + + // Clip bottom + offset = newPosition.y + clippedBounds.origin.y + clippedBounds.size.height - screenBounds.origin.y - screenBounds.size.height - outset; + if (offset > 0.0f) + { + clippedBounds.size.height -= offset; + } + + // Clip right + offset = newPosition.x + clippedBounds.origin.x + clippedBounds.size.width - screenBounds.origin.x - screenBounds.size.width - outset; + if (offset > 0.0f) + { + clippedBounds.size.width -= offset; + } + + if (animated) + { + CABasicAnimation *positionAnimation = [CABasicAnimation animationWithKeyPath:@"position"]; + positionAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + positionAnimation.repeatCount = 0; + positionAnimation.autoreverses = NO; + positionAnimation.fromValue = [NSValue valueWithCGPoint:self.position]; + positionAnimation.toValue = [NSValue valueWithCGPoint:newPosition]; + [self addAnimation:positionAnimation forKey:@"animatePosition"]; + } + + super.position = newPosition; + + // bounds are animated non-clipped but set with clipping + + CGPoint previousNonClippedAnchorPoint = CGPointMake(-previousNonClippedBounds.origin.x / previousNonClippedBounds.size.width, + -previousNonClippedBounds.origin.y / previousNonClippedBounds.size.height); + CGPoint nonClippedAnchorPoint = CGPointMake(-nonClippedBounds.origin.x / nonClippedBounds.size.width, + -nonClippedBounds.origin.y / nonClippedBounds.size.height); + CGPoint clippedAnchorPoint = CGPointMake(-clippedBounds.origin.x / clippedBounds.size.width, + -clippedBounds.origin.y / clippedBounds.size.height); + + if (animated) + { + CABasicAnimation *boundsAnimation = [CABasicAnimation animationWithKeyPath:@"bounds"]; + boundsAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + boundsAnimation.repeatCount = 0; + boundsAnimation.autoreverses = NO; + boundsAnimation.fromValue = [NSValue valueWithCGRect:previousNonClippedBounds]; + boundsAnimation.toValue = [NSValue valueWithCGRect:nonClippedBounds]; + [self addAnimation:boundsAnimation forKey:@"animateBounds"]; + } + + self.bounds = clippedBounds; + previousBounds = clippedBounds; + + // anchorPoint is animated non-clipped but set with clipping + if (animated) + { + CABasicAnimation *anchorPointAnimation = [CABasicAnimation animationWithKeyPath:@"anchorPoint"]; + anchorPointAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + anchorPointAnimation.repeatCount = 0; + anchorPointAnimation.autoreverses = NO; + anchorPointAnimation.fromValue = [NSValue valueWithCGPoint:previousNonClippedAnchorPoint]; + anchorPointAnimation.toValue = [NSValue valueWithCGPoint:nonClippedAnchorPoint]; + [self addAnimation:anchorPointAnimation forKey:@"animateAnchorPoint"]; + } + + self.anchorPoint = clippedAnchorPoint; +} + +#pragma mark - + +- (void)addPointToProjectedPoint:(RMProjectedPoint)point withDrawing:(BOOL)isDrawing +{ + if (isFirstPoint) + { + isFirstPoint = FALSE; + projectedLocation = point; + + self.position = [mapView projectedPointToPixel:projectedLocation]; + + [bezierPath moveToPoint:CGPointMake(0.0f, 0.0f)]; + } + else + { + point.x = point.x - projectedLocation.x; + point.y = point.y - projectedLocation.y; + + if (isDrawing) + [bezierPath addLineToPoint:CGPointMake(point.x, -point.y)]; + else + [bezierPath moveToPoint:CGPointMake(point.x, -point.y)]; + + lastScale = 0.0; + [self recalculateGeometryAnimated:NO]; + } + + [self setNeedsDisplay]; +} + +- (void)moveToProjectedPoint:(RMProjectedPoint)projectedPoint +{ + [self addPointToProjectedPoint:projectedPoint withDrawing:NO]; +} + +- (void)moveToScreenPoint:(CGPoint)point +{ + RMProjectedPoint mercator = [mapView pixelToProjectedPoint:point]; + [self moveToProjectedPoint:mercator]; +} + +- (void)moveToCoordinate:(CLLocationCoordinate2D)coordinate +{ + RMProjectedPoint mercator = [[mapView projection] coordinateToProjectedPoint:coordinate]; + [self moveToProjectedPoint:mercator]; +} + +- (void)addLineToProjectedPoint:(RMProjectedPoint)projectedPoint +{ + [self addPointToProjectedPoint:projectedPoint withDrawing:YES]; +} + +- (void)addLineToScreenPoint:(CGPoint)point +{ + RMProjectedPoint mercator = [mapView pixelToProjectedPoint:point]; + [self addLineToProjectedPoint:mercator]; +} + +- (void)addLineToCoordinate:(CLLocationCoordinate2D)coordinate +{ + RMProjectedPoint mercator = [[mapView projection] coordinateToProjectedPoint:coordinate]; + [self addLineToProjectedPoint:mercator]; +} + +- (void)performBatchOperations:(void (^)(RMShape *aPath))block +{ + ignorePathUpdates = YES; + block(self); + ignorePathUpdates = NO; + + lastScale = 0.0; + [self recalculateGeometryAnimated:NO]; +} + +#pragma mark - Accessors + +- (void)closePath +{ + [bezierPath closePath]; +} + +- (float)lineWidth +{ + return lineWidth; +} + +- (void)setLineWidth:(float)newLineWidth +{ + lineWidth = newLineWidth; + + lastScale = 0.0; + [self recalculateGeometryAnimated:NO]; +} + +- (NSString *)lineCap +{ + return shapeLayer.lineCap; +} + +- (void)setLineCap:(NSString *)newLineCap +{ + shapeLayer.lineCap = newLineCap; + [self setNeedsDisplay]; +} + +- (NSString *)lineJoin +{ + return shapeLayer.lineJoin; +} + +- (void)setLineJoin:(NSString *)newLineJoin +{ + shapeLayer.lineJoin = newLineJoin; + [self setNeedsDisplay]; +} + +- (UIColor *)lineColor +{ + return [UIColor colorWithCGColor:shapeLayer.strokeColor]; +} + +- (void)setLineColor:(UIColor *)aLineColor +{ + if (shapeLayer.strokeColor != aLineColor.CGColor) + { + shapeLayer.strokeColor = aLineColor.CGColor; + [self setNeedsDisplay]; + } +} + +- (UIColor *)fillColor +{ + return [UIColor colorWithCGColor:shapeLayer.fillColor]; +} + +- (void)setFillColor:(UIColor *)aFillColor +{ + if (shapeLayer.fillColor != aFillColor.CGColor) + { + shapeLayer.fillColor = aFillColor.CGColor; + [self setNeedsDisplay]; + } +} + +- (NSString *)fillRule +{ + return shapeLayer.fillRule; +} + +- (void)setFillRule:(NSString *)fillRule +{ + shapeLayer.fillRule = fillRule; +} + +- (CGFloat)lineDashPhase +{ + return shapeLayer.lineDashPhase; +} + +- (void)setLineDashPhase:(CGFloat)dashPhase +{ + shapeLayer.lineDashPhase = dashPhase; +} + +- (void)setPosition:(CGPoint)newPosition animated:(BOOL)animated +{ + if (CGPointEqualToPoint(newPosition, super.position) && CGRectEqualToRect(self.bounds, previousBounds)) + return; + + [self recalculateGeometryAnimated:animated]; +} + +@end diff --git a/MapView/MapView.xcodeproj/project.pbxproj b/MapView/MapView.xcodeproj/project.pbxproj index 290a32e..23b38a5 100755 --- a/MapView/MapView.xcodeproj/project.pbxproj +++ b/MapView/MapView.xcodeproj/project.pbxproj @@ -53,6 +53,8 @@ 16EC85D7133CA6C300219947 /* RMCacheObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 16EC85D1133CA6C300219947 /* RMCacheObject.m */; }; 16F3581B15864135003A3AD9 /* RMMapScrollView.h in Headers */ = {isa = PBXBuildFile; fileRef = 16F3581915864135003A3AD9 /* RMMapScrollView.h */; }; 16F3581C15864135003A3AD9 /* RMMapScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 16F3581A15864135003A3AD9 /* RMMapScrollView.m */; }; + 16F98C961590CFF000FF90CE /* RMShape.h in Headers */ = {isa = PBXBuildFile; fileRef = 16F98C941590CFF000FF90CE /* RMShape.h */; }; + 16F98C971590CFF000FF90CE /* RMShape.m in Sources */ = {isa = PBXBuildFile; fileRef = 16F98C951590CFF000FF90CE /* RMShape.m */; }; 16FAB66413E03D55002F4E1C /* RMQuadTree.h in Headers */ = {isa = PBXBuildFile; fileRef = 16FAB66213E03D55002F4E1C /* RMQuadTree.h */; }; 16FAB66513E03D55002F4E1C /* RMQuadTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 16FAB66313E03D55002F4E1C /* RMQuadTree.m */; }; 16FFF2CB14E3DBF700A170EC /* RMMapQuestOpenAerialSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 16FFF2C914E3DBF700A170EC /* RMMapQuestOpenAerialSource.h */; }; @@ -204,6 +206,8 @@ 16EC85D1133CA6C300219947 /* RMCacheObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMCacheObject.m; sourceTree = "<group>"; }; 16F3581915864135003A3AD9 /* RMMapScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMMapScrollView.h; sourceTree = "<group>"; }; 16F3581A15864135003A3AD9 /* RMMapScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMMapScrollView.m; sourceTree = "<group>"; }; + 16F98C941590CFF000FF90CE /* RMShape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMShape.h; sourceTree = "<group>"; }; + 16F98C951590CFF000FF90CE /* RMShape.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMShape.m; sourceTree = "<group>"; }; 16FAB66213E03D55002F4E1C /* RMQuadTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMQuadTree.h; sourceTree = "<group>"; }; 16FAB66313E03D55002F4E1C /* RMQuadTree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMQuadTree.m; sourceTree = "<group>"; }; 16FFF2C914E3DBF700A170EC /* RMMapQuestOpenAerialSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMMapQuestOpenAerialSource.h; sourceTree = "<group>"; }; @@ -587,6 +591,8 @@ B8F3FC630EA2E792004D8F85 /* RMMarker.m */, B8CEB1C30ED5A3480014C431 /* RMPath.h */, B8CEB1C40ED5A3480014C431 /* RMPath.m */, + 16F98C941590CFF000FF90CE /* RMShape.h */, + 16F98C951590CFF000FF90CE /* RMShape.m */, 25757F4D1291C8640083D504 /* RMCircle.h */, 25757F4E1291C8640083D504 /* RMCircle.m */, ); @@ -677,6 +683,7 @@ 1607499514E120A100D535F5 /* RMGenericMapSource.h in Headers */, 16FFF2CB14E3DBF700A170EC /* RMMapQuestOpenAerialSource.h in Headers */, 16F3581B15864135003A3AD9 /* RMMapScrollView.h in Headers */, + 16F98C961590CFF000FF90CE /* RMShape.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -895,6 +902,7 @@ 1607499614E120A100D535F5 /* RMGenericMapSource.m in Sources */, 16FFF2CC14E3DBF700A170EC /* RMMapQuestOpenAerialSource.m in Sources */, 16F3581C15864135003A3AD9 /* RMMapScrollView.m in Sources */, + 16F98C971590CFF000FF90CE /* RMShape.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; -- libgit2 0.24.0