draw line on MKMapView with pattern image

2019-01-23 00:25发布

问题:

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!

回答1:

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

}


回答2:

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