Friday, February 22, 2013

WhirlyGlobe-Maply 2.1 Beta2

The new beta is out for the Component.  I used to host this stuff on github, but they abruptly stopped letting you do that.  So Dropbox it is.

New Features


  • Lofted Polygons; there's a blog post.  They're pretty.
  • Tweaks to the shape display (cylinders, spheres, etc).
  • The Component now supports locally paged spherical earth quad tree layers.
  • The sample app now does the globe (WhirlyGlobe) and the map (Maply).
  • Lighting!  It's sort of 'eh', but getting better.  At least you can control it now.
  • Shaders... yeah, I'd stay away from this one.  I need it for client work, but I'm not sure what direction it's going to go.
  • Lots and lots of random bug fixes.

This is also the first beta I'm putting out for Maply.  The data structures are the same, and the controls similar.  Just create a MaplyViewController instead of a WhirlyGlobeViewController and you're off.

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.

Friday, January 25, 2013

Image Paging & MapBox Data Sets

Large image dataset paging has been in WhirlyGlobe for several versions now.  With 2.1, it's gotten a lot more efficient.  In this video I discuss some of the improvements and play around with a couple of neat MapBox data sets.


Interfacing With MapBox


The WhirlyGlobe Component has had remote tile support for a while.  Just provide a base URL and it'll start fetching images in a standard tiling scheme.  In honor of the new MapBox data sets, I decided to make this even simpler.

Some data providers will put a TileJSON file at the top level of their data sets.  MapBox does this, of course, and it provides a little extra bit of information and makes the developer's job simpler.

I added support for parsing TileJSON files to the WhirlyGlobe Component.  With a little help from AFNetworking, we can make the code incredibly easy.


// globeViewC is our WhirlyGlobeViewController
// jsonTileSpec is the full URL for the tile spec
// thisCacheDir is a local directory where we'll cache images
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:jsonTileSpec]];
        
AFJSONRequestOperation *operation =
[AFJSONRequestOperation JSONRequestOperationWithRequest:request
  success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
  {
       // Add a quad earth paging layer based on the tile spec we just fetched
       [globeViewC addQuadEarthLayerWithRemoteSource:JSON cache:thisCacheDir];
  }
  failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON)
  {
       NSLog(@"Failed to reach JSON tile spec at: %@",jsonTileSpec);
  }
];
        
[operation start];

There are blocks involved, so the syntax is a bit horrid, but the result is pretty simple.

  • We send out a request for the top level TileJSON file.  
  • When it comes back we hand it over to the addQuadEarthLayerWithRemoteSource: method on our globe view controller.  
  • WhirlyGlobe does the rest.


WhirlyGlobe 2.1


You can find the new functionality and performance improvements in WhirlyGlobe 2.1.  That version is in beta at the moment, but it's quite stable.

I'm adding a few more minor features, mostly based on requests, and that'll be the release version.  Look for a comprehensive feature video soon.


Wednesday, January 23, 2013

New App: Tremor Tracker

This one's been out for a while.  I haven't posted about it because my blogging habits are, at best, random.

So here it is, Tremor Tracker, an app by Andrew Hoyer.

I'm pretty much always going to tap on the red one.

I like that Andrew is not me and has produced an app.  It shows that people who are not me can make these things.  And that's good for my business.

Functionality


The app itself is clean and simple. It shows events in the last hour, day, week, or month.  Magnitudes are sorted by color and size, which helps them stand out as groups.  To see more about an event, you just tap on it and read the text.

That's about it.  A clean, consistent display of data well adapted to the globe format.

API vs. Component


Andrew had actually implemented an earlier version of Tremor Tracker with the raw WhirlyGlobe API, presumably 1.2.  In the latest version he moved to the WhirlyGlobe Component (2.0), which I gather was much easier to use.

He's using newer Component functionality in there too.  The popups use view trackers to tie the UIView to a geo coordinate.

Friday, January 18, 2013

WhirlyGlobe Component 2.1 (beta1)

The beta for WhirlyGlobe 2.1 is now officially out.  I'm tracking the Component and the API versions the same now, mostly because I'm using the Component on client projects.

Can you name all the features?  There will be a quiz.


Where to Get It


How about github?  Develop branch, v2.1_beta1 tag.

Or you could download the binary distribution if you want to skip all that compiling stuff.

What's In It


Tons of new stuff.  Just giant piles of it.  Read the last few blog posts for details.

The biggest internal change is OpenGL ES 2.0 support and that is now turned on by default.  If you want to go back to the older renderer, you can, but I don't think you'll need to.  There's a lot of optimization in the 2.0 renderer and more coming.

I've also switched to an off/on mode for the z-buffer.  It goes like this:
  • Z buffer is off when rendering the globe tiles
  • Still off when rendering 3D markers, stickers, labels, which are priority sorted.
  • Z buffer is turned on to render vectors
  • Still on to render 3D shapes and anything that needs occlusion culling.
  • Still on to render tile skirts.  This is why we don't see crawly black lines all over the place.
The best thing about that approach is we can use priority sorting for most things, then use the z-buffer for really big or weird things like those ballistic lines above.

How Stable Is It


Pretty stable.  I'm using it in several client projects.  Well, variants of it anyway.  Hey, the test app is working fine.

Tuesday, January 15, 2013

New Feature: Auto layout

Label layout is a feature I've been contemplating for a while.  I finally had a client with an urgent enough need to get it done.


Label Layout


It's pretty simple.  You tell WhirlyGlobe how important your screen labels are and it'll do its best to draw them on the screen.

There's a new field in the MaplyScreenLabel so let's take a look.

                    
@interface MaplyScreenLabel : NSObject
{
    /// Put yer user data here
    NSObject *userObject;
    /// Location in geographic (lat/lon) in radians
    MaplyCoordinate loc;
    /// Size on the screen, in points.  In general, set the height, but not the width.
    CGSize size;
    /// Text to display
    NSString *text;
    /// If set, this is the image to use for the marker
    UIImage *iconImage;
    /// Offset the text on screen by this amount.  Defaults to zero.
    CGSize offset;
    /// If set, this color overrides the default
    UIColor *color;
    /// If set, this label can be selected.  On by default.
    bool selectable;
    /// For the label layout engine, this is the importance of this particular
    ///  label.  It's set to MAXFLOAT by defaut, which means it always shows up.
    /// Set it to another value to actually be laid out with constraints.
    float layoutImportance;
}


Yeah, that's getting a little busy.  Anyway, it's that last field: layoutImportance.  By default it's set to MAXFLOAT, which simulates the old behavior (display everything).  All the values are relative, so feel free to set it how you like.  I use a value of 1.0.

And that's all there is.  It's crazy simple to use.  The implementation... less simple.

Implementation


We're only laying out labels right now, but the support is much more generic.  It goes a little bit like this.

The label layer does the heavy lifting of rendering the text, creating textures and handing off geometry to the appropriate parts of the system.  For 3D labels, we create drawables.  For 2D labels, we hand off to the ScreenSpace Generator, a weird little module in the renderer.  Now we also talk to the layout layer.

It's the layout layer that handles (you guessed it) the layout.  After the label layer has created a single label (or more likely 2,000 of them), it bundles up the spatial info and hands that off to the layout layer.  It's up to the layout layer to make its decisions and tell the rendering subsystem what to enable or disable and what offsets to use.

Label layout can get arbitrarily tricky and has been proven to be O(Really Annoying), even if you do a half assed job (cough).  As a result, you don't want to run it every frame: The layout layer runs ever time you stop moving.  It kicks off a dispatch queue, does its calculations and comes up with a list of labels to enable/disable and what offsets to use.  Those are handed off to the rendering portion of the system and the changes show up pretty quickly.

WhirlyGlobe 2.1


This is a big 2.1 feature and it's working in the develop branch on github.  I'll do another binary distribution soon, but honestly, the develop branch isn't that hard to use.  Give it a try.

If you do, I've also turned on OpenGL ES 2.0 support (another big 2.1 feature).  If it causes you any problems (it probably will), go ahead and turn it off like so:

                    
[globeViewC setHints:@{kWGRendererOpenGLVersion: @(1)}];


Monday, January 7, 2013

Cylinders, Spheres and Great Circles - 3D Shape Support

It's a new year!  And that means it's time to catch up on the posts I should have done last year.  Let's start with shapes.

A few weeks ago I added support for 3D shapes.  The WhirlyGlobe API support went in first, followed immediately by Component support for WhirlyGlobe and Maply.  The Components are turning out to be very popular, so it's likely this will be the pattern going forward.

Five basic shapes are working now: Cylinders, Spheres, Great Circles, Lines, and Circles.  Cylinders and spheres are fairly obvious.  Great Circles take a height and do a very nice curve above the earth and lines allow you to scribble in 3-space around the globe.

Let's dig in.

Cylinders and Spheres


The base of each cylinder is tied to the earth, with the long axis pointed upward.  Spheres are just spheres with no real directionality.  You place both of these with lat/lon coordinates and you have the usual control over size, offset from the earth, and color.

So much for, what is that? DC?
That snapshot is from the test app and it's pretty easy to pop these things on the globe.  Let's start by looking at the data structures themselves.

                    
@interface MaplyShapeCylinder : NSObject
{
    /// Center of the base in local coordinates
    MaplyCoordinate baseCenter;
    /// Radius in display units (1.0 is the size of the earth)
    float radius;
    /// Height in display units
    float height;
}


The comments are fairly descriptive here.  You specify a lon/lat coordinate (in radians) for the baseCenter and you set the radius and height in display units.  Remember that display units are based on a sphere with a radius of 1.0; everything else is relative to that.
                    
@interface MaplyShapeSphere : NSObject
{
    /// Center of the sphere in local coordinates
    MaplyCoordinate center;
    /// Radius in display units (1.0 is the size of the earth)
    float radius;
    /// Offset from the globe (in display units)
    float height;
}

The spheres are even simpler.  You specify the center in radians (lon/lat) and the radius and (optional) height in display units.

Then you just collect these babies up in an NSArray and pass that on to the addShapes: method in the WhirlyGlobeViewController.  Here's an example where we add a blue cylinder over Washington, DC.

                    
[globeViewC setShapeDesc:@{kWGColor : [UIColor colorWithRed:0.0 green:0.0 blue:1.0 alpha:0.8]}];
MaplyShapeCylinder *cyl = [[MaplyShapeCylinder alloc] init];
cyl.baseCenter = WGCoordinateMakeWithDegrees(-77.036667, 38.895111);
cyl.radius = 0.01;
cyl.height = 0.06;
[globeViewC addShapes:@[cyl]];
[globeViewC setShapeDesc:@{kWGColor : [NSNull null]}];

That's all there is to it.  Remember to add as many things as possible at once.  The system is going to group those together and optimize the rendering.  Oh, and presently, selection is not supported for shapes.

Great Circles


Users have been asking for something like this for a long while.  The idea is pretty simple, but the implementation takes a little work.  You specify a start and end point on the globe, and a height.  The curve will run from the start to the end, reaching that height right in the middle.  So technically it's only a great circle if height = 0.

No, not technically great circles.  Also, shut up.
Let's take a look at how to specify one.  they're actually pretty simple.
                    
@interface MaplyShapeGreatCircle : NSObject
{
    /// Start and end points in geographic
    MaplyCoordinate startPt,endPt;
    /// Height is related to radius == 1.0 for the earth
    float height;
    /// Line width is in pixels
    float lineWidth;
}

The start and end points are in radians (lon/lat) as always.  A great circle reaches that height in its middle and that's specified in display units.  Remember the radius of the earth is 1.0.  Lastly, these are rendered as lines, so you get to specify a line width.

As with the other shapes, you call addShapes: on your globe view controller.  These will respond to the drawOffset and color parameters in the default shape description, so control them that way.

What's Next


Well that's it for shapes.  Mostly.  I didn't talk about lines or circles.  They're in the header file, you can figure them out, though they're not nearly as interesting.

Great things are afoot with WhirlyGlobe & Maply.  Up next are a couple of apps users have published  (without me, very exciting) and a really neat new feature for interactive auto-layout of labels.  That one needs a video, so it might be a while.