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.


Thursday, December 13, 2012

WhirlyGlobe Component 2.1 (Alpha)

I've put together an Alpha distribution for the new WhirlyGlobe Component.  The naming scheme is changing to match the API.  Because.

The next version of WhirlyGlobe is 2.1, so this is the WhirlyGlobe-Maply Component 2.1 (alpha).  I'm sure it'll live up to that designation.

It sure doesn't *look* unstable.

New Features

There is a ton of new stuff in WhirlyGlobe 2.1 and the Component.  Here's a short, non-exhaustive list.

  • Continuous zoom mode in the view.  Let's us get much closer to the surface of the globe and much farther away.
  • Better animate to position support, including offsets.
  • More rendering hints, including a new Zbuffer variant just for globes.
  • OpenGL ES 2.0, but it's off.  Leave it off (for now).
  • More locational feedback from the view controller, mostly utility methods.
  • Vector databases (shape files) with simple queries.
  • 3D shapes, including spheres, circles, cylinders, lines, and ballistic curves.
  • Stickers, which are like markers, but bigger.  And curved.
  • Invisible vectors used just for selection.
  • More layer control.
  • Private headers for WhirlyGlobeViewController if you want to subclass.  Not for the faint of heart.
  • Explicit stop/start animation calls.  Don't know why they weren't there before.
  • A userObject on the selectable objects.  Nobody wanted to subclass, so here you go.
  • Retina support is finally complete and properly debugged.
  • Automatic 2D label layout.  That's right.  You heard me.
In addition to all the new features, things just work better.  Quad paging layers are much better optimized.  Try it with OpenStreetMap... it's kind of astounding.

To get it, use the develop branch on github.  If you'd rather have a binary distribution, drop me a line and I'll send you the link.

No doubt I've broken something critical and left something out.  The sooner I find it, the sooner I can call this "beta".

Monday, December 10, 2012

WhirlyGlobe/CartoDB demo and AppNation IV

José Navarro put together this neat little demo a few months ago.  It uses WhirlyGlobe and CartoDB, which is something I'd like to see more of.  Seems like a natural combination.


He talks about it a little on his blog.  Looks like he was using the raw API since it was pre-Component... which is impressive.  Now some of that would be a bit easier.

Anyway, neat little example and it's fun to see people using WhirlyGlobe.  Especially people who don't make me sign an NDA and swear never to reveal our work together.  [cough]

APPNATION IV


I'm going to be at APPNATION this week.  Yes, apparently it is all caps.  It's the PREMIER app conference that's within walking distance of my house and happened to give me free exhibit space to hock my wares.  So hocking I shall be.

My clients are usually the big firms that make apps for even bigger firms.  This seems like the sort of thing they might send their project managers to.  So I'm stalking users, basically.

Wednesday, November 28, 2012

WhirlyGlobe Component - Version 1.10

A few weeks ago I published Version 1.01 for the Component.  Shortly afterwards, I pulled it.  The retina support was a mess.

The New Version


So here is Version 1.10.  It's actually been out for a while and users have been downloading it.  So far, no complaints.

That clears up the retina support, fixes a bug with MBTiles, and adds the pole coverage for Spherical Mercator projections.

Use Version 1.10 for the foreseeable future.  The next one will be based on WhirlyGlobe 2.1, which has a ton of changes.

More Stuff


I'm doing quite a lot of development on WhirlyGlobe at the moment.  Some of the goodies include:

  • OpenGL ES 2.0 support (shaders!)
  • A 3D shape layer - Spheres, cylinders, parabolic curves
  • Stickers and chunks - Useful for slapping an image over a geographic area
  • Lots and lots of bug fixes and performance improvements

In other news, my clients like the Components.  That means lots of development on those and most new features represented immediately.  The quad paging layers have been thoroughly tested in the develop branch and are scary good with a high performance data source.

All of this stuff goes into client projects first.  I can rarely show those, and never when they're under development.  So you'll just have to take my word that Bing imagery looks fantastic on the globe with a Retina display.  Until I work up an example anyway.

Thursday, November 1, 2012

WhirlyGlobe Component: Vectors

I'm running a bit behind on new describing new functionality.    A few weeks ago I added vector support to the WhirlyGlobe Component.

The toolkit itself has had vector support since 1.0.  That's kinda my thing.  Images are everyone else's thing, though so that's what went in to the Component first.

Some Vectors, no doubt.
So great, how do they work?

WGVectorObject

The WGVectorObject is a wrapper around the more complex classes in the WhirlyGlobe API.  If you want better control, go use those.  If you want to do a few simple operations, WGVectorObject will do.

A WGVectorObject can represent one or more features.  It's a little goofy, but makes sense once you start slinging them around.  Just treat them as somewhat opaque.

First thing you'll want to do is load them.  There are a variety of ways.

Vectors From GeoJSON

As the heading implies, you can load GeoJSON.  Here's a quick example that looks for a named file in the bundle, loads that into a WGVectorObject and then adds it to the globe.

                    
NSString *fileName = [[NSBundle mainBundle] pathForResource:name ofType:@"geojson"];
if (fileName)
{
  NSData *jsonData = [NSData dataWithContentsOfFile:fileName];
  if (jsonData)
  {
    WGVectorObject *wgVecObj = [WGVectorObject VectorObjectFromGeoJSON:jsonData];
    WGComponentObject *compObj = [globeViewC addVectors:[NSArray arrayWithObject:wgVecObj]];
  }
}

If you're generating the files yourself or reading from a remote service, it's nice, but it's slow.  Who would think parsing geometry out of a text file would be slow?  I'm shocked.

Vectors From Scratch

You can also create vector data from scratch.  You can make points, linears, and areals.  The following methods are part of WGVectorObject.
                    
/// Construct with a single point
- (id)initWithPoint:(WGCoordinate *)coord attributes:(NSDictionary *)attr;

/// Construct with a linear feature (e.g. line string)
- (id)initWithLineString:(WGCoordinate *)coords numCoords:(int)numCoords attributes:(NSDictionary *)attr;

/// Construct as an areal with an exterior
- (id)initWithAreal:(WGCoordinate *)coords numCoords:(int)numCoords attributes:(NSDictionary *)attr;

/// Add a hole to an existing areal feature
- (void)addHole:(WGCoordinate *)coords numCoords:(int)numCoords;

Just gather your WGCoordinate structures into an array and pass them in for linear or areal.  For points, just pass in the one and if you need to create holes (in an areal), call that method after you've created the areal.

Vectors From Shapefiles

Lastly, you can read vector data out of Shapefiles.  I wrap these things in a database-like object so I can do (somewhat) fast lookups.  The object you want is called a MaplyVectorDatabase, but it's only in the source tree at the moment.  I haven't updated the pre-compiled Component.

I'm exposing functionality as I need it, so the MaplyVectorDatabase object just does the following for now.
                    
/// Construct from a shapefile in the bundle
+ (MaplyVectorDatabase *) vectorDatabaseWithShape:(NSString *)shapeName;

/// Return vectors that match the given SQL query
- (MaplyVectorObject *)fetchMatchingVectors:(NSString *)sqlQuery;

/// Search for all the areals that surround the given point (in geographic)
- (MaplyVectorObject *)fetchArealsForPoint:(MaplyCoordinate)coord;

Maply?  Yes, Maply.  All new functionality is Maply, with WhirlyGlobe reserved for things that are globe specific.  Surprisingly, that's very little.

Anyway, the basic idea with a MaplyVectorDatabase is that you create it with that shapefile name and then run simple queries against it.  I've exposed the fetch matching vectors and fetch areals for point (e.g. point in poly search).  That's enough to reimplement the WhirlyGlobeApp, but I'm sure I'll expose more in the future.

As with the WhirlyGlobe API, the Component is going to build cache files next to your shapefiles.  There will be one name.mbr and one name.sqlite file.  If they don't exist, the toolkit will build them and that'll be sloooow.  So grab them out of the simulator and stick them in your bundle.

Displaying Vectors

Once you've got some vectors, you can display them.  As always, bigger batches are better, smaller are slower.  The call to add vectors to a WhirlyGlobeViewController is just like so.

WGComponentObject *compObj = [globeViewC addVectors:[NSArray arrayWithObject:wgVecObj]];

Vectors are then eligible for selection and they'll turn up in your globe view controller delegate like everything else.  They show up after labels and markers in priority, as their search is run last.

Upcoming Stuff

There's a lot more in the pipeline for WhirlyGlobe, Maply, and the Components.  Most of my time is devoted to WhirlyGlobe/Maply projects for clients and they're all spinning stuff back in to the toolkits.  Some of it is structural, such as OpenGL ES 2.0 support and some is pure features, such as a Shape layer.

Coming up next is WhirlyGlobe 2.1, which will be a minor release of what's in the master branch.  Primarily tweaks for retina, caps for the Spherical Mercator layers, and random bug fixes.