Category Archives: 3D on Mapview

OpenGL 3D rendering on iPhone mapview.

OpenGL on top of Mapview part 1

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!

iPhone MkMapview and MKAnnotation, the beauty of them.


iphone mkmapview

iphone mkmapview with annotation upto v3.x

Ok. let’s start from iPhone mapview.  When iPhone first hit the market back in ’07, I wasn’t able to get my hands on one right away. But, one thing that struck me like a thunderbolt was the fact that *every iphone* on street had an ability to locate where its user was.

Back in ’05 when I hassled my ass around J2ME, GPS-equipped mobile phones were super rare and everyone just hoped in their sweetest dream that every mobile phone would one day have it. Then barely after 2 years, it happened on my naked eyes.  How shameless…

On top of it, iPhone was capable to display the beautiful Google map. When Steve casually called a Starbucks from his iphone map screen, I almost got an heart attack. What it meant was a wide, crazy amount of opportunity to do entirely insane things like never before.

When I finally start coding on MKMapView class this year, however, things aren’t so pretty. Not that I have a huge complain because I vividly recall what it was like to display a map on J2ME device.  Even black and white images with no means of scroll and multi-touch were God-send back then.  Now what, a colorful map as big as your palm leaps and bounds, and I call it “not so perdy”? I’m a whiny little baby, yeah I know.

Let’s look at a screen shot and get to the detail. The screen above is a typical MKMapView (map itself) and MKMapAnnotations (red pins). It’s quite useful and displays wherever place you feel worthy. But the issue is you only have red pins.  Ok, you can have a little info banner on top of them, but that is it.

Now in iOS 4, Apple enables map annotations to be an image icon such that prominent apps like Foursquare now displays places with icons. Things have evolved quite a bit here. Let’s take a look.

Foursquare "Places" screen

With just a few different simple icons, your iphone map becomes more visual and shortens the time to prioritize places according to your need. Just few icons do that.

Yet, it’s only 2D, and I don’t know about you but the world I live in is full of colorful 3D objects, not red pins, and white squares, damn it!

On top of that nothing animates; no movements, no color changes, no blinking arrows to show where hot events are happening right now, and many more.

Plus, when you zoom out, icons stack on top of each other so that you can’t see what’s what very clearly. Well, this isn’t likely impossible to solve, but requires different size of icons for each zoom level.

We can solve all these issues with OpenGL 3D. iPhone has a very powerful 3D graphic chipset as we all know, and OpenGLES 1.0 is supported by even the oldest hardware. (If you want to find out how powerful iPhone 3D graphics is, just look here, Unreal Epic Citadel. if you haven’t seen it, I’m sure your jaw will drop.)

In short, what I was aiming at was pretty much like Tomtom iphone GPS’ 3D map view with higher FPS. In Tomtom’s product, map scrolls underneath of 3D objects when you are navigating. Objects changes their sizes and all the road signs keep facing you as you move along. Imagine that happens on your Foursquare screen.

You see what I’m getting at, right?

I watched their video clips very carefully, and thought ‘ok, that’s easy, just overlap 3D view on top of MKMapView and we’re done!’ Well, if it was that easy, I wouldn’t write a blog about it, would I? :P

Anyway, here is a screen shot I rendered a few months ago, and I’ll continue from 3D rendering part next time.

3D stars on MKMapView.

Next : OpenGL on top of Mapview part 1

Rendering 3D objects on iPhone map view.

I’m going to post a series of topics about rendering 3D objects on iPhone map view.

I’ve been working on this for a good while,  and am finally ready to share what I have discovered.  Not everything is very fantastic since Apple has *THE* upper hand to control every aspects of how objects are shown on their device.  But, there are always way to get around that and I’ll cover that too.  So, stay tuned. (well, I know my blog does not have oh-so-many audiences, so if you find upcoming entries interesting, please share on twitter and facebook. :)

*Wordpress plugins for sharing on Twitter and Facebook are just installed and tested. Thanks awe.sm team for providing those! (sorry, I don’t use you guys’ service.)
  1. iPhone MkMapview and MKAnnotation, the beauty of them.
  2. OpenGL on top of Mapview part 1