Thursday, February 21, 2013

The Return of Lofted Polygons

You know that one feature? The one you implement right near the beginning of your work; that you come to regret almost immediately, but can't get rid of? This is that feature.

Africa has the best looking stats.

Yeah, that.  Looks great, doesn't it?  I hate it.

I whipped up lofted polygons at a hackathon several years back and everyone's loved it since.  We won a prize, because... c'mon, I'm a professional consultant who specializes in mobile 3D.  It's hardly even fair.

This feature has been my computational portrait of Dorian Gray.

Why Lofted Polys are so Slow

Because math.  Check this out.

Geez, Canada.  What is that?  One vertex per resident?

And remember, this is on an iPad.  When the user taps, something needs to happen within a few frames.  So what's the answer?  Well, you save the expensive representation out to storage.  You cache.

I've had caching for lofted polys at the WhirlyGlobe API level for a while, but it was ugly.  This is a display toolkit and unexpectedly writing things to storage is not particularly welcome.  When the Component came along, I just buried the feature.

Lofted Polys in the WhirlyGlobe-Maply Component

Now lofted polys are back and here's how they work.  There are a couple of new methods on the view controller.

/// Color
#define kMaplyColor @"color"
/// Height above the ground
#define kMaplyLoftedPolyHeight @"height"
/// Boolean that turns on/off top (on by default)
#define kMaplyLoftedPolyTop @"top"
/// Boolean that turns on/off sides (on by default)
#define kMaplyLoftedPolySide @"side"

/// Add visual defaults for the lofted polys
- (void)setLoftedPolyDesc:(NSDictionary *)desc;

/// Add one or more lofted polys.
/// If you pass in a vector database, we'll attempt to cache
///  the generated data with 'key' for speed.  The vector database should
///  be where the polys originated.  nil is acceptable for both key and cacheDb.
- (MaplyComponentObject *)addLoftedPolys:(NSArray *)polys key:(NSString *)key cache:(MaplyVectorDatabase *)cacheDb;

The comments say it all.  There's the lofted poly description dictionary with which you can supply height (in display units), turn off top and sides and assign color.  Then there's the add call and that's where the caching comes in.

If your vectors came from a MaplyVectorDatabase (probably a shape file), then you can cache the lofted polys back to that database.  For shapefiles, that just means a bunch of .loftcache files with the same name.  You'll also need to provide a unique string key for the cache.  I use the admin attribute for countries, but you can make something up.

Here's what all that usually looks like.

[viewC setLoftedPolyDesc:@{kMaplyColor: [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.25], kMaplyLoftedPolyHeight: @(0.05)}];
MaplyComponentObject *compObj = [baseViewC addLoftedPolys:@[vecObj] key:countryName cache:countryDb];
[viewC setLoftedPolyDesc:@{kMaplyColor: [NSNull null], kMaplyLoftedPolyHeight: [NSNull null]}];

That's basically it.  It's up to you to make up a key.  If you want to skip caching, just pass nil into key and cache.

What's Next

That's the last high level feature I needed to expose to the WhirlyGlobe-Maply Component (it works in the flat map too).  There are a few low level features, like active models, but I'm not going to expose those in the Component.  That's crazy stuff used by crazy people.

This is checked into the develop branch on github.  If you want it right now, go get it.  If not, there will be a binary beta distribution for WhirlyGlobe-Maply 2.1 along soon.