iosmkmapviewmkoverlay

draw line on MKMapView with pattern image


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 line drawn, but it seems that the drawing is done using only the left, topmost pixel of the pattern image, instead of the whole image.

Here is my drawing routine:

void drawPatternCellCallback(void *info, CGContextRef cgContext)
{
    UIImage *patternImage = [UIImage imageNamed:@"tmpLine"];
    CGContextDrawImage(cgContext, CGRectMake(0, 0, patternImage.size.width, patternImage.size.height), patternImage.CGImage);

}

- (void)drawMapRect:(MKMapRect)mapRect
          zoomScale:(MKZoomScale)zoomScale
          inContext:(CGContextRef)context
{
    float alpha = 1;        
    float tileW = 6.0f;
    float tileH = 4.0f;
    CGFloat lineWidth = MKRoadWidthAtZoomScale(zoomScale)*2;
    CGMutablePathRef path = CGPathCreateMutable();


    if (path != nil)

    {
        //setup styles
        CGContextSetRGBStrokeColor(context, 0.0f, 0.0f, 1.0f, 0.5f);

        const CGPatternCallbacks kPatternCallbacks = {0, drawPatternCellCallback, NULL};
        CGPatternRef strokePattern = CGPatternCreate(
                                                     NULL,
                                                     CGRectMake(0, 0, tileW, tileH), 
                                                     CGAffineTransformIdentity,
                                                     tileW, // horizontal spacing
                                                     tileH,// vertical spacing
                                                     kCGPatternTilingConstantSpacing,
                                                     true,
                                                     &kPatternCallbacks);
        //color sapce
        CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
        CGContextSetStrokeColorSpace(context, patternSpace);

        //pattern
        CGContextSetStrokePattern(context, strokePattern, &alpha);

        //joins/ends
        CGContextSetLineJoin(context, kCGLineJoinMiter);
        CGContextSetLineCap(context, kCGLineCapButt);
        CGContextSetLineWidth(context, lineWidth);

        //OK, let's draw it
        CGPoint firstCGPoint = [self pointForMapPoint:self.point1];    
        CGPoint lastCGPoint = [self pointForMapPoint:self.point2];
        CGPathMoveToPoint(path, NULL, lastCGPoint.x, lastCGPoint.y);    
        CGPathAddLineToPoint(path, NULL, firstCGPoint.x, firstCGPoint.y);
        CGContextAddPath(context, path);
        CGContextStrokePath(context);

        //house hold
        CGPathRelease(path);
        CGPatternRelease(strokePattern);
        CGColorSpaceRelease(patternSpace);
    }
}

Any idea what's wrong?

Thanx!


Solution

  • 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];
        }
    
    }