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.
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 the开发者_StackOverflowRect = [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.
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.
I'm not sure if this would affect the scaling issue, but in your OverlayView code, your drawing the entire image for every maptile.
Have you tried only drawing the portion of the image that is visible in mapRect?
When I've had problems with MKOverlayViews, its been helpful for me to draw the rect of the overlay (self.overlay.boundingRect) and the mapRect (passed into drawMapRect). Although, I'm not sure if drawing the mapRect would be helpful in your situation.
At any rate, heres the function I use to draw the rect in case you want to try it out
-(void)drawRect:(MKMapRect)rect inContext:(CGContextRef)context withLineWidth:(float)lineWidth andColor:(CGColorRef)color
{
double maxx = MKMapRectGetMaxX(rect);
double minx = MKMapRectGetMinX(rect);
double maxy = MKMapRectGetMaxY(rect);
double miny = MKMapRectGetMinY(rect);
CGPoint tr = [self pointForMapPoint:(MKMapPoint) {maxx, maxy}];
CGPoint br = [self pointForMapPoint:(MKMapPoint) {maxx, miny}];
CGPoint bl = [self pointForMapPoint:(MKMapPoint) {minx, miny}];
CGPoint tl = [self pointForMapPoint:(MKMapPoint) {minx, maxy}];
CGMutablePathRef cgPath = CGPathCreateMutable();
CGPathMoveToPoint(cgPath, NULL, tr.x, tr.y);
CGPathAddLineToPoint(cgPath, NULL, br.x, br.y);
CGPathAddLineToPoint(cgPath, NULL, bl.x, bl.y);
CGPathAddLineToPoint(cgPath, NULL, tl.x, tl.y);
CGPathAddLineToPoint(cgPath, NULL, tr.x, tr.y);
CGContextSaveGState(context);
CGContextAddPath(context, cgPath);
CGContextSetStrokeColorWithColor(context, color);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, lineWidth);
CGContextStrokePath(context);
CGPathRelease(cgPath);
CGContextRestoreGState(context);
}
My guess is that Google Maps is stretching the image non-linearly to compensate for the map projection and that your code/Apple's code isn't.
One possible solution would be to subdivide the overlay image into smaller rectangles and call MKMapPointForCoordinate() separately for each rectangle. Then the data will be much closer to being correct.
WGS-84 data is using UTM projection, MKMapView is using mercator. You are using different methods to map a 3D object to a 2D surface, hence it is not giving the same results.
You'll need to go down in GDAL and move the incomming image to a different projection. Let me know if you figured it out, because it ain't an easy
Your image/overlay is most likely no longer proportional (i.e. it's been stretched). I've seen this kind of thing in my own app where the center of the view is correct, but as you go away from the equator toward either pole (top and bottom of screen) the map/overlay becomes increasingly distorted.
You are obviously scaling your image by scaleFactor. I'd take a look at that for starters.
size_t width = (CGImageGetWidth(imageReference) / self.scaleFactor);
size_t height = (CGImageGetHeight(imageReference) / self.scaleFactor);
Another good way to test your code to see if scaling is the culprit, is to scale your MKMapView down to the size of the overlay image. Leave the overlay image alone, and if it is no longer distorted then you know that is the problem.
Apple has a sample app from WWDC that parses KML and displays it on a map. If you are a paid developer you can access it from the WWDC videos page in iTunes. I recommend using their parser.
精彩评论