Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to display an image on a MKOverlayView?

UPDATE:

Images who are projected on the MKMapView using a MKOverlayView use the Mercator projection, while the image that I use as input data uses a WGS84 projection. Is there a way to convert the input image, to the right projection WGS84 -> Mercator, without tiling the image up and can it done on the fly?

Normally you could convert a image to right projection using the program gdal2tiles. The input data however changes every fifteen minutes, so the image has to be converted every fifteen minutes. So the conversion has to be done on the fly. I also want the tiling to be done by Mapkit and not by myself using gdal2tiles or the GDAL framework.

UPDATE END

I'm currently working on a project which displays a rainfall radar over some part of the world. The radar image is provided by EUMETSAT, they offer a KML file which can be loaded into Google Earth or Google Maps. If I load the KML file in Google Maps it displays perfectly, but if I draw the image using a MKOverlayView on a MKMapView, the image is slightly of.

For example, on the left side, Google Maps and on the right side the same image is displayed at a MKMapView.

alt text

alt text

The surface that the image covers can be viewed on Google Maps, the satellite that is used for the image is the "Meteosat 0 Degree" satellite.

The surface that both images cover is of the same size, this is the LatLonBox from the KML file, it specifies where the top, bottom, right, and left sides of a bounding box for the ground overlay are aligned.

  <LatLonBox id="GE_MET0D_VP-MPE-latlonbox">
        <north>57.4922</north>
        <south>-57.4922</south>
        <east>57.4922</east>
        <west>-57.4922</west>
        <rotation>0</rotation>
  </LatLonBox>

I create a new custom MKOverlay object called RadarOverlay with these parameters,

[[RadarOverlay alloc] initWithImageData:[[self.currentRadarData objectAtIndex:0] valueForKey:@"Image"] withLowerLeftCoordinate:CLLocationCoordinate2DMake(-57.4922, -57.4922) withUpperRightCoordinate:CLLocationCoordinate2DMake(57.4922, 57.4922)];

The implementation of the custom MKOverlay object; RadarOverlay

- (id) initWithImageData:(NSData*) imageData withLowerLeftCoordinate:(CLLocationCoordinate2D)lowerLeftCoordinate withUpperRightCoordinate:(CLLocationCoordinate2D)upperRightCoordinate
{
     self.radarData = imageData;

     MKMapPoint lowerLeft = MKMapPointForCoordinate(lowerLeftCoordinate);
     MKMapPoint upperRight = MKMapPointForCoordinate(upperRightCoordinate);

     mapRect = MKMapRectMake(lowerLeft.x, upperRight.y, upperRight.x - lowerLeft.x, lowerLeft.y - upperRight.y);

     return self;
}

- (CLLocationCoordinate2D)coordinate
{
     return MKCoordinateForMapPoint(MKMapPointMake(MKMapRectGetMidX(mapRect), MKMapRectGetMidY(mapRect)));
}

- (MKMapRect)boundingMapRect
{
     return mapRect;
}

The implementation of the custom MKOverlayView, RadarOverlayView

- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context
{
    RadarOverlay* radarOverlay = (RadarOverlay*) self.overlay;

    UIImage *image          = [[UIImage alloc] initWithData:radarOverlay.radarData];

    CGImageRef imageReference = image.CGImage;

    MKMapRect theMapRect    = [self.overlay boundingMapRect];
   CGRect theRect           = [self rectForMapRect:theMapRect];
    CGRect clipRect     = [self rectForMapRect:mapRect];

    NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
    CGContextSetAlpha(context, [preferences floatForKey:@"RadarTransparency"]);

    CGContextAddRect(context, clipRect);
    CGContextClip(context);

    CGContextDrawImage(context, theRect, imageReference);

    [image release]; 
}

When I download the image, I flip the image so it can be easily drawn in the MKOverlayView

size_t width    = (CGImageGetWidth(imageReference) / self.scaleFactor);
size_t height   = (CGImageGetHeight(imageReference) / self.scaleFactor);

// Calculate colorspace for the specified image
CGColorSpaceRef imageColorSpace = CGImageGetColorSpace(imageReference);

// Allocate and clear memory for the data of the image
unsigned char *imageData = (unsigned char*) malloc(height * width * 4);
memset(imageData, 0, height * width * 4);

// Define the rect for the image
CGRect imageRect;
if(image.imageOrientation==UIImageOrientationUp || image.imageOrientation==UIImageOrientationDown) 
    imageRect = CGRectMake(0, 0, width, height); 
else 
    imageRect = CGRectMake(0, 0, height, width); 

// Create the imagecontext by defining the colorspace and the address of the location to store the data
CGContextRef imageContext = CGBitmapContextCreate(imageData, width, height, 8, width * 4, imageColorSpace, kCGImageAlphaPremultipliedLast);

CGContextSaveGState(imageContext);

// Scale the image to the opposite orientation so it can be easylier drawn with CGContectDrawImage
CGContextTranslateCTM(imageContext, 0, height);
CGContextScaleCTM(imageContext, 1.0, -1.0);

if(image.imageOrientation==UIImageOrientationLeft) 
{
    CGContextRotateCTM(imageContext, M_PI / 2);
    CGContextTranslateCTM(imageContext, 0, -width);
}
else if(image.imageOrientation==UIImageOrientationRight) 
{
    CGContextRotateCTM(imageContext, - M_PI / 2);
    CGContextTranslateCTM(imageContext, -height, 0);
} 
else if(image.imageOrientation==UIImageOrientationDown) 
{
    CGContextTranslateCTM(imageContext, width, height);
    CGContextRotateCTM(imageContext, M_PI);
}

// Draw the image in the context
CGContextDrawImage(imageContext, imageRect, imageReference);
CGContextRestoreGState(imageContext);

After I flipped the image, I manipulate it and then store it in memory as a NSData object.

It looks like the image got stretched, but it looks allright at the center of the image, which is at the equator.

like image 999
Jeroen de Leeuw Avatar asked Oct 08 '10 15:10

Jeroen de Leeuw


1 Answers

Have you already seen "Session 127 - Customizing Maps with Overlays" from the WWDC 2010 videos? One of the examples takes earthquake data, which gives the earthquake risk for 0.5 by 0.5 degree areas and maps them. Your radar data looks similar, based on squares. The sample code has a full application called HazardMaps, which takes this data and creates an overlay using MKMapPoints. If you haven't already seen this video, I think it will give you plenty of useful information. He also talks about converting to the Mercator projection.

Another thing to check is what coordinate system (datum) the data from EUMETSAT is in. Google Maps uses a system called WGS-84, which is a general standard. But there are many other standards which can give more accurate positions in different parts of the world. If you use the latitude and longitude from a different standard in Google Maps, all your points will be off by a certain amount. The offset is not consistent, it changes as you move around the map. It's possible that Google Maps is being smart about the data and converting to WGS-84 on the fly.

You might find out more details by looking at the KML. I looked but couldn't find the final KML, with the rectangles. Perhaps it gives information about what coordinate system it's using in the metadata.

like image 192
nevan king Avatar answered Sep 18 '22 22:09

nevan king