Wednesday, November 18, 2015

How To: Pre-Process Tilemap Overlays in MKMapView

Hi all.

Time for another tutorial. Recently I was trying to modify a tilemap returned from a typical public map overlay server (such as openstreetmap.org) - basically I needed a way to change black areas of the tile into transparent so that I could nicely overlay it on top of MKMapView. I searched for this for a while, found some solutions but did not really work as I wanted. Some examples used MKTileOverlayRenderer. Somehow that gave me weird results that I do not understand.


Note: In this tutorial there are no downloadable project since it is a straight forward MKTileOverlay subclassing usage. (But, if u really need a sample project, do shout in the comment section).

In your custom MKTileOverlay, you return the URL to load according to map rect using the normal URLForTilePath as usual. Then in the loadTileAtPath method, you process the *data variable.

Here is the example of the method:



- (void)loadTileAtPath:(MKTileOverlayPath)path result:(void (^)(NSData *, NSError *))result
{
    if (!result)
    {
        return;
    }
   
        NSURLRequest *request = [NSURLRequest requestWithURL:[self URLForTilePath:path]];
        [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
            
            if ([data length]<335) {
                
                result(nil,nil);
            } else {
                
                    CGSize sz = CGSizeMake(256,256);
                    CGRect rect = CGRectMake(0,0,256,256);
                    UIImage *img = [UIImage imageWithData:data];
                    
                    UIGraphicsBeginImageContext(sz);
                    CGContextRef context = UIGraphicsGetCurrentContext();
                    
                    [img drawInRect:rect];
                    
                    UInt8 *dat = CGBitmapContextGetData(context);
                    // int numComponents = 4;
                    long dataLength = CGBitmapContextGetHeight(context) * CGBitmapContextGetBytesPerRow(context);
                    
                    int  Comp1, Comp2, Comp3, Comp4;
                    
                    Comp1 = 0;
                    Comp2 = 1;
                    Comp3 = 2;
                    Comp4 = 3;

                    // This processes each pixels and convert the map to greyscale.

                    for(int index=0;index<dataLength;index+=4){
                        int aRed = dat[index+Comp1];
                        int aGrn = dat[index+Comp2];
                        int aBlu = dat[index+Comp3];
                        
                        float grey = (aRed+aGrn+aBlu)/3.0;
                        
                        dat[index+Comp1] = grey;
                         dat[index+Comp2] = grey;
                         dat[index+Comp3] = grey;
                        
                    }
                    
                    UIImage *tileImage = UIGraphicsGetImageFromCurrentImageContext();
                    UIGraphicsEndImageContext();
                    NSData *tileData = UIImagePNGRepresentation(tileImage);
                    result(tileData,nil); // return new data


                  
            }
        }];

}

As in the method above: Take *data and convert it to UIImage. Create a graphic context to draw on. Draw the map on that context. Then further manipulate the context pixels by pixels. Finally get the resulting image from the modified context and return as result.

Normal original openstreetmap tile (unmodified) loaded unto MKMapView as MKTileOverlay:




Change to grey (pixels to grey code given in the method above):



Change to invert color:


Change to High Saturation


Note: The air pollution markers are on another MKTileOverlay.

All these 4 map styles came from a single tileserver (openstreetmap.org).

Do take note that each time a tile (of size 256x256) is loaded, your custom class will process it with CoreGraphics with the code given. It is wise to keep the processing simple and not too heavy burden on processor.


1 comment:

  1. Hello, I would be very interested in a sample project (as you propose!)
    Thks !

    ReplyDelete