It’s been a while since I wrote the second post about 3D on map. I have had to juggle few balls constantly up on air, so please bear with me.
Before moving on, I’d like to point out that there are *MANY* ways to achieve what I am trying here; “draw interesting places/events in 3D on map”. However the case might be, you might have to consider one very important factor, MONEY.
For example, you can buy and store full pre-rendered World map into iPhone. Then load piece by piece like 2D texture into OpenGL space to render from map to stars. Hmmm… I don’t think that I can even buy the map myself.
Another one is to walk straight up to Eric Schmidt and strike a deal for Google 3D map service. If I could do this, I shouldn’t even write these posts.
You can go with full set of map library like Route-me and map tile services like Cloudmade. Route-me is free and Cloudmade is free for small amount of load. Unfortunately even this is rather costly approach for low-profile start-ups like me. Just take rough estimate of workload and monthly service charge when your project goes over the “free” limit. Hmmm…
This post is all about “Make it pretty and make it cheap”. What’s left is then to come up with a way to combine OpenGL and iPhone mapview (MKMapView class). MkMapView is Cocoa class that provides Google map with smooth scroll and zoom. Besides, it calculates latitude and longitude of screen position on Mercator projection and vice versa. As with other native Cocoa classes, subclassing and categorizing it are fine so you have very powerful resource at your disposal already. Let’s get started from the two. By the way, if you have any better idea, let me know. I crave for it all the times.
*A map tile is small piece of Mercator projection of World map at a latitude and longitude. The piece-wise fragmentation of map makes loading World map into lightweight environments like web browsers and iPhone possible and feasible. Meanwhile, you’d need a rather non-trivial amount calculation to place map tiles on correct locations of screen. In addition, the size of entire map tiles for World is humongous. This is why you need map library and map tile service. Offline map tile storage is possible but just take a look at the size of Tom-tom GPS. Don’t forget it does not cover the entire World at various scales.
**Currently Google does not provide map tile service. Microsoft Bing and Yahoo! Map provide it though. You might have to pay handsomely beforehand. Cloudmade isn’t completely free. Nonetheless it is a newcomer compared to three former and price is rather reasonable. Plus, Cloudmade staffs talk with their customers.)
OpenGLES version and EAGLView
Ok, let me pick up from what was left off last time. 3D rendering on iPhone requires to use an OpenGLES (OpenGL for Embedded Systems, A good place for memory refresher is Jeff Lamarche’s OpenGL ES from the Ground Up.) and a special view, namely EAGLView.
For the version of OpenGLES to use, (Of course, there are versions! Even bible has versions of new and old!) you can choose 1.1 to cover every iPhone/iPod Touch on earth, or 2.0 to cover from iPhone 3GS /iPod Touch 3rd gen. onward. Without a doubt, OpenGLES 2.0 is much more pretty and powerful in displaying 3D objects.
Since I want to reach as broad audiences as there possibly are, my choice is definitely to go with OpenGLES 1.1, and all my examples here are written with OpenGLES 1.1 in mind. If you want to apply them to OpenGLES 2.0, you might have different outcomes than what I have.
Cocoa does not provide native EAGLView class. EAGLView is what Xcode hands you from template when you choose to make an “OpenGL ES Application”. If you take a look inside, you’ll notice it is just a plain UIView subclass with its CALayer configured for custom OpenGL rendering.
EAGLView and MKMapView come together
The first thing you need to do is to stack two views: EAGLView (OpenGLES), and MKMapview (Mapview).
Stacking EAGLView and MKMapView is as simple as adding UIViews into UIViewController. Just add MKMapView first and EAGLView later.
// MKMapView initializing... MKCoordinateRegion region; region.center.latitude = 37.712615; region.center.longitude = -122.472839; region.span.longitudeDelta = 14.0625; [(MKMapView *)self.view setRegion:region animated:NO]; //EAGLView (in OpenGLES 1.x) initializing... EAGLContext *aContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1]; self.context = aContext; [aContext release]; [oglesView setContext:context]; [oglesView setFramebuffer]; [self.view addSubview:oglesView];
Once you stack them up, you might want to run Timer or CADisplayLink to render 60 frames for every second (60 fps). (There might be other ways to animate 3D objects such as using event trigger. But I have not tested yet nor confronted solid examples so I cannot tell you drop timer off.) Since iPhone hardware screen refresh rate is locked into 60 fps, you don’t need go faster than that.
Transparent EAGLView background
Once you start render and draw, what you’ll see is an oscillating colorful rectangle with gray background. In order to see the MKMapView in the back, you need to make EAGLView transparent. You need to touch two places. (Transparent EAGLView does not require OpenGL alpha blending. Unless you really need to use, you don’t have to turn on blending feature.)
// EAGLView.m
//-(id)initWithCoder:(NSCoder*)coder
//UIView background transparency
self.opaque = NO;
self.backgroundColor = [UIColor clearColor];
//Then CAEAGLLayer background transparency
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
eaglLayer.opaque = NO;
//work around for "unavailable error"
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
const CGFloat myColor[] = {0.0, 0.0, 0.0, 0.0};
eaglLayer.backgroundColor = CGColorCreate(rgb, myColor);
CGColorSpaceRelease(rgb);
Then when you draw a frame, you clear the whole screen with alpha value of 0.0. As long as your alpha is 0.0, you can use any color.
//mapviewViewController.m //-(void)drawFrame glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
Compile it and run, and then things look just fine. The mapview will display bay area of San Francisco, and 3D objects (in this case just a 2D rectangle) will be displayed on top of the map. What a breeze!
MkMapView and Re-routing UIEvent
When you start scroll the mapview, you will notice the mapview doesn’t really budge. You can zoom in and out but that is it.
…..ok. What’s going on?
What’s really peculiar about MKMapView class is it does not want anything to sit on top of it. If a UIView does so, MKMapView will not accept UIEvent. It has behaved this way ever since.
To get around this issue,
1) we need to firstly set EAGLView’s “userInteractionEnabled” to “NO”. Yes, that is right. This will completely block every UIEvent coming through.
2) Then we’ll have to make a UIWindow subclass to re-route touch event to MKMapView.
3) Just in case you still want to receive user event, I put a hole in EAGLView with “-(void) handleTouches:(UIEvent*)event”.
Look at the code below.
// EAGLView.m
-(id)initWithCoder:(NSCoder*)coder
// U don't want this view to listen user touch event
self.userInteractionEnabled = NO;
}
- (void) handleTouches:(UIEvent*)event {
}
UIWindow subclass and how it is implemented in app delegate.
@implementation EventRouteWindow
@synthesize overlayListener;
- (void)sendEvent:(UIEvent*)event {
[super sendEvent:event];
if(overlayListener != nil && [overlayListener respondsToSelector:@selector(handleTouches:)])
[overlayListener performSelector:@selector(handleTouches:) withObject:event];
}
@end
@implementation AppDelegate
-(void)applicationDidFinishLaunching:(NSNotification*)aNotification{
[self.window addSubview:viewController.view];
[(EventRouteWindow*)window setOverlayListener:[viewController oglesView]];
[self.window makeKeyAndVisible];
}
@end
As we get in “-(void)sendEvent:(UIEvent*)event” of UIWindow class, we are touching the lowest possible level of UIEvent delivery chain. With the UIEvent object, you can do just about anything. In fact, touches* function family of UIView handle just derivatives of UIEvent object delivered from here.
If we’re to draw an overview of how this event routing works, it should look like following.

Isn’t this dirty hack of some sort, you just ask? Yes, kind of.
However, this is not part of private framework that might put you in a trouble later.
Are there other options to achieve this workaround? Of course, there are many, if not thousands. I just don’t like objects at the same logical level exchanging pointers from one to another.
Ok, let’s compile and run again, and now map will scroll and zoom. You will quickly notice OpenGL will stop rendering whenever you touch the screen. Just gentle touch will put your renderer in halt. Even if there is no link whatsoever to connect the two views, your renderer will stop until you pull of your finger from screen.
In fact, this is why I started blogging about it. There still remain a few issues to work around. My good friend, I’d like to take a journey with you on conquering those monsters. I’ll cover why this is happening, and how we can get around it next time.
If you have any question, find a mistake, and/or just want to say hello, drop me a line at twitter.com/stkim1. Download and enjoy the example!


it’s very nice!
Your example is fantastic. But I can’t find the mapview.zip.
May I take a peek at it?
Cheers!
-Mark
Mark, the broken link is now fixed.
thankyou Sung-Taek,
I stumbled accross this blog looking to solve the opengl pausing on mapview gesture problem myself, any hint on how it is done or latter parts of this blog?
Thanks for having interested in my mapview post.
I’m currently preparing next mapview post from time to time, and I’ll post it as soon as it’s completed. What happens is iOS puts an iron grip on OpenGL call priority when system call competes user call. That means when Mapview starts scrolling, zooming, and etc, you are going to sit in the back.
In order to get around the management, we have two options; 1) using a separated and dedicated render thread for opengl call, and 2) use UIScrollView delegate to update render frame when touch input comes in. The overview of first options is very clearly demonstrated and illustration in http://fabiensanglard.net/doomIphone/index.php Fabien wrote up very very excellently. For the second option, I’d say take a look at Apple document for UIScrollView. I have to dig up my code so I don’t clearly recall what method it is but there is one you can use for updating every render frame when touch event freezes your render timer.
Hi,
if you use NSRunLoopCommonModes instead of NSDefaultRunLoopMode, your OpenGL view will be updated while the map is dragged:
[aDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
Did you find any solutions to let the OpenGL view behave like an overlay (move according to the map like an annotation and scale with the zoom)?
Dear Christoph, I think you just provided the last piece of puzzle for my problem. I have found a solution for OpenGL overlay but when you drag the map long enough, it still halts my renderer. I am currently working on other project but I will soon test and post my solution and put them in github. Thank you very much.
Do you have any good ideas for getting the OpenGL view to appear underneath the other overlays and annotations?
Ross, I have not *really* checked but I don’t think that is possible at all. The annotations and overlays should be drawn within MKMapView. Even if you’re to successfully dig into the class and do some nice work with it, chances might be high that the work would not be accepted to store. But honestly, I have not looked into it. Hope someone gives you better answer.
What do you think about somehow putting the GLView inside of a MKAnnotationView or MKOverlayView?
Ross,
I’ve quickly glanced over MKAnnotationView and MKOverlayView and I believe MKOverlayView might be the one you might want to subclass. it *seems* MapKit framework handles each MKAnnotation as an independent entity so that you might end up having multiple OpenGLES context in a single view. I don’t know how well iOS could handle multiple contexts so I cannot tell.
MKOverlayView, however, handles visible area of MapView so that you might give it a shot to cover the whole mapview and put OpenGLES context in it.
in the official document,
The fundamental issue is that every UIView in iOS seems to have OpenGLES context; all the nice scoll, zoom, transition, and fading in/out animation cannot be handled superbly like that without the power of OpenGLES hardware acceleration. So much so that iOS doesn’t like its base classes being covered with a custom OpenGLES view. Think about what could happen if you cover UITableView with EAGLView. iOS would not be very happy about it and the render performance would nose dive.
I am currently working on a contract work for a social network. It is scheduled to be completed by the end of this month. I will perform various experiments on other classes and write few more posts about it after that.
Thank you very much for tips.
Did you ever get a chance to work with this anymore?
Nope. I started this back in Spring ’10, and there wasn’t a single entity who barely shown interest in this. So, I came back and went away now and then things are turning around now. Guess what, google map just turned their web app into 3D a coule weeks ago. (It’s in experiment mode now IIRC) It will come to mobile within 1 or maximum 2 year frame, I suppose.
I’m currently rebuilding protocol stack since 4sq has updated their API from v1 to v2. What’s astonishing to me is, the more I try to make my renderer go smoother, the heavier I have to focus on lower layers such as protocol, CoreData handler, and such b/c they tend to eat a lot of process power. Keep in mind that this project should cover as lowly powered devices as 3GS, iOS 4.0.
I will put my source in github sometime later when it reaches a mark I set. All is well and I am making progress. Thanks for keeping your interest.
This looks very interesting, would you mind if I ask how you can map the CGPoint on the MKMap with the Point on EAGLView?
Never mind, I found the solution already, it’s here
http://developer.apple.com/library/IOs/#documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instm/UIView/convertPoint:toView:
Thank you for this nice example. In the example project I noticed an issue with the framerate. It clearly shows that the bouncing box animates in a low framerate. The weird thing however, is that manipulating (zooming/pinching) the mapview just the right way can make the animation framerate rice to a steady 60fps from 40fps. Any ideas what is causing this?