I try to draw a line on a MKMapView
with a pattern image.
The drawing is done by adding a custom MKMapOverlay
view.
I\'m able to get the l
I ended up with a completely different strategy.
Instead of adding by own overlay I now rely on MKPolyLine
.
Using the following code I'm able to add a pseudo animated line from point A to point B on a MKMapView
.
The code adds several overlays to the MKMapViews with a slight delay, giving the impression of an animation
Not the most beautiful solution! - but it looks pretty good in action :-)
/*start the animation*/
-(void)plotRouteOnMap
{
[self.mapView removeOverlays:self.mapView.overlays];
//calculate a number locations between the two locations
self.points = [self getPointsOnRouteFrom:<FROM_LOCATION>
to:<TO_LOCATION>
onMapView:self.mapView];
[self addOverlaysFromPointsWithStartFrom:[NSNumber numberWithInt:1]];
}
/*convert a CGPoint to a CLLocation according to a mapView*/
- (CLLocation*)pointToLocation:(MKMapView *)mapView fromPoint:(CGPoint)fromPoint
{
CLLocationCoordinate2D coord = [mapView convertPoint:fromPoint toCoordinateFromView:mapView];
return [[[CLLocation alloc] initWithLatitude:coord.latitude longitude:coord.longitude] autorelease];
}
/*get a list of Location objects between from and to*/
-(NSArray*)getPointsOnRouteFrom:(CLLocation*)from to:(CLLocation*)to onMapView:(MKMapView*)mapView
{
int NUMBER_OF_PIXELS_TO_SKIP =10; //lower number will give a more smooth animation, but will result in more layers
NSMutableArray *ret = [NSMutableArray array];
CGPoint fromPoint = [mapView convertCoordinate:from.coordinate toPointToView:mapView];
CGPoint toPoint = [mapView convertCoordinate:to.coordinate toPointToView:mapView];
NSArray *allPixels = [self getAllPointsFromPoint:fromPoint toPoint:toPoint];
for (int i = 0 ; i < [allPixels count] ; i+=NUMBER_OF_PIXELS_TO_SKIP) {
NSValue *pointVal = [allPixels objectAtIndex:i];
[ret addObject:[self pointToLocation:mapView fromPoint:[pointVal CGPointValue]]];
}
[ret addObject:[self pointToLocation:mapView fromPoint:toPoint]];
return ret;
}
/*calulate alle pixels from point to toPint*/
-(NSArray*)getAllPointsFromPoint:(CGPoint)fPoint toPoint:(CGPoint)tPoint
{
/*Simplyfied implementation of Bresenham's line algoritme */
NSMutableArray *ret = [NSMutableArray array];
float deltaX = fabsf(tPoint.x - fPoint.x);
float deltaY = fabsf(tPoint.y - fPoint.y);
float x = fPoint.x;
float y = fPoint.y;
float err = deltaX-deltaY;
float sx = -0.5;
float sy = -0.5;
if(fPoint.x<tPoint.x)
sx = 0.5;
if(fPoint.y<tPoint.y)
sy = 0.5;
do {
[ret addObject:[NSValue valueWithCGPoint:CGPointMake(x, y)]];
float e = 2*err;
if(e > -deltaY)
{
err -=deltaY;
x +=sx;
}
if(e < deltaX)
{
err +=deltaX;
y+=sy;
}
} while (round(x) != round(tPoint.x) && round(y) != round(tPoint.y));
[ret addObject:[NSValue valueWithCGPoint:tPoint]];//add final point
return ret;
}
/*add a poly line overlay to mapview which start at position 0 and end in 'end' in the array points*/
-(void)addOverlaysFromPointsWithStartFrom:(NSNumber*)end
{
int intEnd = [end intValue];
//construct polyline view from start
CLLocationCoordinate2D *locations = malloc(sizeof(CLLocationCoordinate2D)*2);
CLLocation *loc1 = (CLLocation*)[points objectAtIndex:0];
CLLocation *loc2= (CLLocation*)[points objectAtIndex:intEnd];
locations[0] = loc1.coordinate;
locations[1] = loc2.coordinate;
MKPolyline *line = [MKPolyline polylineWithCoordinates:locations count:2];
[self.mapView addOverlay:line];
if((intEnd+1) < [points count])//add more overlays after delays unless this is the endpoint
{
[self performSelector:@selector(addOverlaysFromPointsWithStartFrom:) withObject:[NSNumber numberWithInt:intEnd + 1] afterDelay:0.01];
}
}
Swift version of accepted answer
Add image as overlay using MKOverlayRenderer
func addLayersOfAnimatingOverlay() {
let sourcePoint = // enter as CLLocation
let destinationPoint = // enter as CLLocation
let pointsCoordinatesArray = self.getLocationArrayFrom(startLocation: sourcePoint, endLocation: destinationPoint)
//add overlay on above coordinates
DispatchQueue.main.async{
self.addDirectionOverlayInMap(locationArray: self.pointsCoordinates1, title: "1")
}
To get coordinates in a MKPolyline
func getLocationArrayFrom(startLocation: CLLocation, endLocation: CLLocation) -> [CLLocationCoordinate2D] {
var coordinatesArray: [CLLocationCoordinate2D] = []
if let points = helperClass.getPointsOnRoute(from: startLocation, to: endLocation, on: mapView) {
for point in points {
let coordinate = point.coordinate
coordinatesArray.append(coordinate)
}
}
return coordinatesArray
}
//MARK: get cordinates from line
func getPointsOnRoute(from: CLLocation?, to: CLLocation?, on mapView: MKMapView?) -> [CLLocation]? {
let NUMBER_OF_PIXELS_TO_SKIP: Int = 120
//lower number will give a more smooth animation, but will result in more layers
var ret = [Any]()
var fromPoint: CGPoint? = nil
if let aCoordinate = from?.coordinate {
fromPoint = mapView?.convert(aCoordinate, toPointTo: mapView)
}
var toPoint: CGPoint? = nil
if let aCoordinate = to?.coordinate {
toPoint = mapView?.convert(aCoordinate, toPointTo: mapView)
}
let allPixels = getAllPoints(from: fromPoint!, to: toPoint!)
var i = 0
while i < (allPixels?.count)! {
let pointVal = allPixels![i] as? NSValue
ret.append(point(toLocation: mapView, from: (pointVal?.cgPointValue)!)!)
i += NUMBER_OF_PIXELS_TO_SKIP
}
ret.append(point(toLocation: mapView, from: toPoint!)!)
return ret as? [CLLocation]
}
/**convert a CGPoint to a CLLocation according to a mapView*/
func point(toLocation mapView: MKMapView?, from fromPoint: CGPoint) -> CLLocation? {
let coord: CLLocationCoordinate2D? = mapView?.convert(fromPoint, toCoordinateFrom: mapView)
return CLLocation(latitude: coord?.latitude ?? 0, longitude: coord?.longitude ?? 0)
}
func getAllPoints(from fPoint: CGPoint, to tPoint: CGPoint) -> [Any]? {
/*Simplyfied implementation of Bresenham's line algoritme */
var ret = [AnyHashable]()
let deltaX: Float = fabsf(Float(tPoint.x - fPoint.x))
let deltaY: Float = fabsf(Float(tPoint.y - fPoint.y))
var x: Float = Float(fPoint.x)
var y: Float = Float(fPoint.y)
var err: Float = deltaX - deltaY
var sx: Float = -0.5
var sy: Float = -0.5
if fPoint.x < tPoint.x {
sx = 0.5
}
if fPoint.y < tPoint.y {
sy = 0.5
}
repeat {
ret.append(NSValue(cgPoint: CGPoint(x: CGFloat(x), y: CGFloat(y))))
let e: Float = 2 * err
if e > -deltaY {
err -= deltaY
x += sx
}
if e < deltaX {
err += deltaX
y += sy
}
} while round(Float(x)) != round(Float(tPoint.x)) && round(Float(y)) != round(Float(tPoint.y))
ret.append(NSValue(cgPoint: tPoint))
//add final point
return ret
}
This will give the following effect (without animation)
The project can be found here